export function useDeleteProject() {
  const queryClient = useQueryClient()

  return useMutation({
    mutationFn: (name: string) => api.deleteProject(name),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['projects'] })
    },
  })
}

export function useDeleteProject() {
  const queryClient = useQueryClient()

  return useMutation({
    mutationFn: ({ name, deleteFiles = false }: { name: string; deleteFiles?: boolean }) =>
      api.deleteProject(name, deleteFiles),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['projects'] })
    },
  })
}


The file C:\Users\MikeSwanson\claude-projects\Autocode-remix\Autocoder-2.0\ui\src\hooks\useProjects.ts has been updated successfully.

export function useDeleteProject() {
  const queryClient = useQueryClient()

  return useMutation({
    mutationFn: ({ name, deleteFiles = false }: { name: string; deleteFiles?: boolean }) =>
      api.deleteProject(name, deleteFiles),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['projects'] })
    },
  })
}

/**
 * React Query hooks for project data
 */

import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import * as api from '../lib/api'
import type { FeatureCreate } from '../lib/types'

// ============================================================================
// Projects
// ============================================================================

export function useProjects() {
  return useQuery({
    queryKey: ['projects'],
    queryFn: api.listProjects,
  })
}

export function useProject(name: string | null) {
  return useQuery({
    queryKey: ['project', name],
    queryFn: () => api.getProject(name!),
    enabled: !!name,
  })
}

export function useCreateProject() {
  const queryClient = useQueryClient()

  return useMutation({
    mutationFn: ({ name, path, specMethod }: { name: string; path: string; specMethod?: 'claude' | 'manual' }) =>
      api.createProject(name, path, specMethod),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['projects'] })
    },
  })
}

export function useDeleteProject() {
  const queryClient = useQueryClient()

  return useMutation({
    mutationFn: (name: string) => api.deleteProject(name),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['projects'] })
    },
  })
}

export function useImportProject() {
  const queryClient = useQueryClient()

  return useMutation({
    mutationFn: ({ name, path }: { name: string; path: string }) =>
      api.importProject(name, path),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['projects'] })
    },
  })
}

// ============================================================================
// Features
// ============================================================================

export function useFeatures(projectName: string | null) {
  return useQuery({
    queryKey: ['features', projectName],
    queryFn: () => api.listFeatures(projectName!),
    enabled: !!projectName,
    refetchInterval: 5000, // Refetch every 5 seconds for real-time updates
  })
}

export function useCreateFeature(projectName: string) {
  const queryClient = useQueryClient()

  return useMutation({
    mutationFn: (feature: FeatureCreate) => api.createFeature(projectName, feature),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['features', projectName] })
    },
  })
}

export function useDeleteFeature(projectName: string) {
  const queryClient = useQueryClient()

  return useMutation({
    mutationFn: (featureId: number) => api.deleteFeature(projectName, featureId),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['features', projectName] })
    },
  })
}

export function useSkipFeature(projectName: string) {
  const queryClient = useQueryClient()

  return useMutation({
    mutationFn: (featureId: number) => api.skipFeature(projectName, featureId),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['features', projectName] })
    },
  })
}

// ============================================================================
// Agent
// ============================================================================

export function useAgentStatus(projectName: string | null) {
  return useQuery({
    queryKey: ['agent-status', projectName],
    queryFn: () => api.getAgentStatus(projectName!),
    enabled: !!projectName,
    refetchInterval: 3000, // Poll every 3 seconds
  })
}

export function useStartAgent(projectName: string) {
  const queryClient = useQueryClient()

  return useMutation({
    mutationFn: (yoloMode: boolean = false) => api.startAgent(projectName, yoloMode),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['agent-status', projectName] })
    },
  })
}

export function useStopAgent(projectName: string) {
  const queryClient = useQueryClient()

  return useMutation({
    mutationFn: () => api.stopAgent(projectName),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['agent-status', projectName] })
    },
  })
}

export function usePauseAgent(projectName: string) {
  const queryClient = useQueryClient()

  return useMutation({
    mutationFn: () => api.pauseAgent(projectName),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['agent-status', projectName] })
    },
  })
}

export function useResumeAgent(projectName: string) {
  const queryClient = useQueryClient()

  return useMutation({
    mutationFn: () => api.resumeAgent(projectName),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['agent-status', projectName] })
    },
  })
}

// ============================================================================
// Setup
// ============================================================================

export function useSetupStatus() {
  return useQuery({
    queryKey: ['setup-status'],
    queryFn: api.getSetupStatus,
    staleTime: 60000, // Cache for 1 minute
  })
}

export function useHealthCheck() {
  return useQuery({
    queryKey: ['health'],
    queryFn: api.healthCheck,
    retry: false,
  })
}

// ============================================================================
// Filesystem
// ============================================================================

export function useListDirectory(path?: string) {
  return useQuery({
    queryKey: ['filesystem', 'list', path],
    queryFn: () => api.listDirectory(path),
  })
}

export function useCreateDirectory() {
  const queryClient = useQueryClient()

  return useMutation({
    mutationFn: (path: string) => api.createDirectory(path),
    onSuccess: (_, path) => {
      // Invalidate parent directory listing
      const parentPath = path.split('/').slice(0, -1).join('/') || undefined
      queryClient.invalidateQueries({ queryKey: ['filesystem', 'list', parentPath] })
    },
  })
}

export function useValidatePath() {
  return useMutation({
    mutationFn: (path: string) => api.validatePath(path),
  })
}

-    mutationFn: (name: string) => api.deleteProject(name),

+    mutationFn: ({ name, deleteFiles = false }: { name: string; deleteFiles?: boolean }) =>

+      api.deleteProject(name, deleteFiles),


Now I need to create a DeleteProjectModal component with:
- Confirmation dialog
- Checkbox for "also delete files from disk"
- Warning about irreversibility when deleting filesLet me create that component.


Now let me create the DeleteProjectModal component:


Write

/**
 * Delete Project Modal
 *
 * Confirmation dialog for deleting a project with option to delete files from disk.
 */

import { useState } from 'react'
import { AlertTriangle, Trash2, X, Loader2, FolderX } from 'lucide-react'
import { useDeleteProject } from '../hooks/useProjects'
import type { ProjectSummary } from '../lib/types'

interface DeleteProjectModalProps {
  project: ProjectSummary
  isOpen: boolean
  onClose: () => void
  onDeleted: () => void
}

export function DeleteProjectModal({
  project,
  isOpen,
  onClose,
  onDeleted,
}: DeleteProjectModalProps) {
  const [deleteFiles, setDeleteFiles] = useState(false)
  const [confirmText, setConfirmText] = useState('')
  const deleteProject = useDeleteProject()

  if (!isOpen) return null

  const requiresConfirmation = deleteFiles
  const isConfirmed = !requiresConfirmation || confirmText === project.name

  const handleDelete = async () => {
    if (!isConfirmed) return

    try {
      await deleteProject.mutateAsync({
        name: project.name,
        deleteFiles,
      })
      onDeleted()
      onClose()
    } catch (error) {
      // Error is handled by React Query
      console.error('Failed to delete project:', error)
    }
  }

  const handleClose = () => {
    if (deleteProject.isPending) return
    setDeleteFiles(false)
    setConfirmText('')
    onClose()
  }

  return (
    <>
      {/* Backdrop */}

        {/* Modal */}

 e.stopPropagation()}
        >
          {/* Header */}

              <Trash2 size={20} />

Delete Project

            <button
              onClick={handleClose}
              className="p-1 hover:bg-gray-100 rounded"
              disabled={deleteProject.isPending}
            >
              <X size={20} />
            </button>

          {/* Content */}

              Are you sure you want to delete {project.name}?

            {/* Project info */}

                <FolderX size={16} />
                {project.path}

              {project.stats.total > 0 && (

                  {project.stats.passing} / {project.stats.total} features completed

              )}

            {/* Delete files checkbox */}

              <input
                type="checkbox"
                checked={deleteFiles}
                onChange={(e) => {
                  setDeleteFiles(e.target.checked)
                  if (!e.target.checked) setConfirmText('')
                }}
                className="mt-1 w-4 h-4 accent-red-600"
                disabled={deleteProject.isPending}
              />

                  <AlertTriangle size={16} className="text-yellow-600" />
                  Also delete files from disk

                  This will permanently delete all project files and cannot be undone.

            {/* Confirmation input when deleting files */}
            {deleteFiles && (

                  <AlertTriangle size={16} />
                  Confirm Deletion

                  Type {project.name} to confirm:

                <input
                  type="text"
                  value={confirmText}
                  onChange={(e) => setConfirmText(e.target.value)}
                  placeholder={project.name}
                  className="w-full neo-input font-mono text-sm"
                  disabled={deleteProject.isPending}
                  autoFocus
                />

            )}

            {/* Error message */}
            {deleteProject.isError && (

                {deleteProject.error instanceof Error
                  ? deleteProject.error.message
                  : 'Failed to delete project'}

            )}

          {/* Footer */}

            <button
              onClick={handleClose}
              className="neo-btn bg-white"
              disabled={deleteProject.isPending}
            >
              Cancel
            </button>
            <button
              onClick={handleDelete}
              disabled={!isConfirmed || deleteProject.isPending}
              className={`neo-btn ${
                deleteFiles
                  ? 'bg-red-500 text-white hover:bg-red-600'
                  : 'bg-[var(--color-neo-pending)] hover:brightness-95'
              } disabled:opacity-50 disabled:cursor-not-allowed`}
            >
              {deleteProject.isPending ? (
                <>
                  <Loader2 size={16} className="animate-spin" />
                  Deleting...
                </>
              ) : (
                <>
                  <Trash2 size={16} />
                  {deleteFiles ? 'Delete Project & Files' : 'Remove from Registry'}
                </>
              )}
            </button>

    </>
  )
}


File created successfully at: C:\Users\MikeSwanson\claude-projects\Autocode-remix\Autocoder-2.0\ui\src\components\DeleteProjectModal.tsx

/**
 * Delete Project Modal
 *
 * Confirmation dialog for deleting a project with option to delete files from disk.
 */

import { useState } from 'react'
import { AlertTriangle, Trash2, X, Loader2, FolderX } from 'lucide-react'
import { useDeleteProject } from '../hooks/useProjects'
import type { ProjectSummary } from '../lib/types'

interface DeleteProjectModalProps {
  project: ProjectSummary
  isOpen: boolean
  onClose: () => void
  onDeleted: () => void
}

export function DeleteProjectModal({
  project,
  isOpen,
  onClose,
  onDeleted,
}: DeleteProjectModalProps) {
  const [deleteFiles, setDeleteFiles] = useState(false)
  const [confirmText, setConfirmText] = useState('')
  const deleteProject = useDeleteProject()

  if (!isOpen) return null

  const requiresConfirmation = deleteFiles
  const isConfirmed = !requiresConfirmation || confirmText === project.name

  const handleDelete = async () => {
    if (!isConfirmed) return

    try {
      await deleteProject.mutateAsync({
        name: project.name,
        deleteFiles,
      })
      onDeleted()
      onClose()
    } catch (error) {
      // Error is handled by React Query
      console.error('Failed to delete project:', error)
    }
  }

  const handleClose = () => {
    if (deleteProject.isPending) return
    setDeleteFiles(false)
    setConfirmText('')
    onClose()
  }

  return (
    <>
      {/* Backdrop */}

        {/* Modal */}

 e.stopPropagation()}
        >
          {/* Header */}

              <Trash2 size={20} />

Delete Project

            <button
              onClick={handleClose}
              className="p-1 hover:bg-gray-100 rounded"
              disabled={deleteProject.isPending}
            >
              <X size={20} />
            </button>

          {/* Content */}

              Are you sure you want to delete {project.name}?

            {/* Project info */}

                <FolderX size={16} />
                {project.path}

              {project.stats.total > 0 && (

                  {project.stats.passing} / {project.stats.total} features completed

              )}

            {/* Delete files checkbox */}

              <input
                type="checkbox"
                checked={deleteFiles}
                onChange={(e) => {
                  setDeleteFiles(e.target.checked)
                  if (!e.target.checked) setConfirmText('')
                }}
                className="mt-1 w-4 h-4 accent-red-600"
                disabled={deleteProject.isPending}
              />

                  <AlertTriangle size={16} className="text-yellow-600" />
                  Also delete files from disk

                  This will permanently delete all project files and cannot be undone.

            {/* Confirmation input when deleting files */}
            {deleteFiles && (

                  <AlertTriangle size={16} />
                  Confirm Deletion

                  Type {project.name} to confirm:

                <input
                  type="text"
                  value={confirmText}
                  onChange={(e) => setConfirmText(e.target.value)}
                  placeholder={project.name}
                  className="w-full neo-input font-mono text-sm"
                  disabled={deleteProject.isPending}
                  autoFocus
                />

            )}

            {/* Error message */}
            {deleteProject.isError && (

                {deleteProject.error instanceof Error
                  ? deleteProject.error.message
                  : 'Failed to delete project'}

            )}

          {/* Footer */}

            <button
              onClick={handleClose}
              className="neo-btn bg-white"
              disabled={deleteProject.isPending}
            >
              Cancel
            </button>
            <button
              onClick={handleDelete}
              disabled={!isConfirmed || deleteProject.isPending}
              className={`neo-btn ${
                deleteFiles
                  ? 'bg-red-500 text-white hover:bg-red-600'
                  : 'bg-[var(--color-neo-pending)] hover:brightness-95'
              } disabled:opacity-50 disabled:cursor-not-allowed`}
            >
              {deleteProject.isPending ? (
                <>
                  <Loader2 size={16} className="animate-spin" />
                  Deleting...
                </>
              ) : (
                <>
                  <Trash2 size={16} />
                  {deleteFiles ? 'Delete Project & Files' : 'Remove from Registry'}
                </>
              )}
            </button>

    </>
  )
}


Now I need to update the ProjectSelector to include a delete button. Let me add a delete icon to each project in the dropdown that triggers the delete modal.


Now update ProjectSelector to add delete functionality:


Edit

import { useState } from 'react'
import { ChevronDown, Plus, FolderOpen, Loader2 } from 'lucide-react'
import type { ProjectSummary } from '../lib/types'
import { NewProjectModal } from './NewProjectModal'

import { useState } from 'react'
import { ChevronDown, Plus, FolderOpen, Loader2, Trash2 } from 'lucide-react'
import type { ProjectSummary } from '../lib/types'
import { NewProjectModal } from './NewProjectModal'
import { DeleteProjectModal } from './DeleteProjectModal'


The file C:\Users\MikeSwanson\claude-projects\Autocode-remix\Autocoder-2.0\ui\src\components\ProjectSelector.tsx has been updated successfully.

import { useState } from 'react'
import { ChevronDown, Plus, FolderOpen, Loader2, Trash2 } from 'lucide-react'
import type { ProjectSummary } from '../lib/types'
import { NewProjectModal } from './NewProjectModal'
import { DeleteProjectModal } from './DeleteProjectModal'

import { useState } from 'react'
import { ChevronDown, Plus, FolderOpen, Loader2 } from 'lucide-react'
import type { ProjectSummary } from '../lib/types'
import { NewProjectModal } from './NewProjectModal'

interface ProjectSelectorProps {
  projects: ProjectSummary[]
  selectedProject: string | null
  onSelectProject: (name: string | null) => void
  isLoading: boolean
}

export function ProjectSelector({
  projects,
  selectedProject,
  onSelectProject,
  isLoading,
}: ProjectSelectorProps) {
  const [isOpen, setIsOpen] = useState(false)
  const [showNewProjectModal, setShowNewProjectModal] = useState(false)

  const handleProjectCreated = (projectName: string) => {
    onSelectProject(projectName)
    setIsOpen(false)
  }

  const selectedProjectData = projects.find(p => p.name === selectedProject)

  return (

      {/* Dropdown Trigger */}
      <button
        onClick={() => setIsOpen(!isOpen)}
        className="neo-btn bg-white text-[var(--color-neo-text)] min-w-[200px] justify-between"
        disabled={isLoading}
      >
        {isLoading ? (
          <Loader2 size={18} className="animate-spin" />
        ) : selectedProject ? (
          <>

              <FolderOpen size={18} />
              {selectedProject}

            {selectedProjectData && selectedProjectData.stats.total > 0 && (

                {selectedProjectData.stats.percentage}%

            )}
          </>
        ) : (

            Select Project

        )}
        <ChevronDown size={18} className={`transition-transform ${isOpen ? 'rotate-180' : ''}`} />
      </button>

      {/* Dropdown Menu */}
      {isOpen && (
        <>
          {/* Backdrop */}

 setIsOpen(false)}
          />

          {/* Menu */}

            {projects.length > 0 ? (

                {projects.map(project => (
                  <button
                    key={project.name}
                    onClick={() => {
                      onSelectProject(project.name)
                      setIsOpen(false)
                    }}
                    className={`w-full neo-dropdown-item flex items-center justify-between ${
                      project.name === selectedProject
                        ? 'bg-[var(--color-neo-pending)]'
                        : ''
                    }`}
                  >

                      <FolderOpen size={16} />
                      {project.name}

                    {project.stats.total > 0 && (

                        {project.stats.passing}/{project.stats.total}

                    )}
                  </button>
                ))}

            ) : (

                No projects yet

            )}

            {/* Divider */}

            {/* Create New */}
            <button
              onClick={() => {
                setShowNewProjectModal(true)
                setIsOpen(false)
              }}
              className="w-full neo-dropdown-item flex items-center gap-2 font-bold"
            >
              <Plus size={16} />
              New Project
            </button>

        </>
      )}

      {/* New Project Modal */}
      <NewProjectModal
        isOpen={showNewProjectModal}
        onClose={() => setShowNewProjectModal(false)}
        onProjectCreated={handleProjectCreated}
      />

  )
}

-import { ChevronDown, Plus, FolderOpen, Loader2 } from 'lucide-react'

+import { ChevronDown, Plus, FolderOpen, Loader2, Trash2 } from 'lucide-react'

+import { DeleteProjectModal } from './DeleteProjectModal'


Now I need to add the state and handlers for the delete modal, and add a delete button to each project item.


export function ProjectSelector({
  projects,
  selectedProject,
  onSelectProject,
  isLoading,
}: ProjectSelectorProps) {
  const [isOpen, setIsOpen] = useState(false)
  const [showNewProjectModal, setShowNewProjectModal] = useState(false)

  const handleProjectCreated = (projectName: string) => {
    onSelectProject(projectName)
    setIsOpen(false)
  }

export function ProjectSelector({
  projects,
  selectedProject,
  onSelectProject,
  isLoading,
}: ProjectSelectorProps) {
  const [isOpen, setIsOpen] = useState(false)
  const [showNewProjectModal, setShowNewProjectModal] = useState(false)
  const [projectToDelete, setProjectToDelete] = useState<ProjectSummary | null>(null)

  const handleProjectCreated = (projectName: string) => {
    onSelectProject(projectName)
    setIsOpen(false)
  }

  const handleDeleteClick = (e: React.MouseEvent, project: ProjectSummary) => {
    e.stopPropagation()
    setProjectToDelete(project)
    setIsOpen(false)
  }

  const handleProjectDeleted = () => {
    // If we deleted the currently selected project, clear selection
    if (projectToDelete?.name === selectedProject) {
      onSelectProject(null)
    }
    setProjectToDelete(null)
  }


export function ProjectSelector({
  projects,
  selectedProject,
  onSelectProject,
  isLoading,
}: ProjectSelectorProps) {
  const [isOpen, setIsOpen] = useState(false)
  const [showNewProjectModal, setShowNewProjectModal] = useState(false)
  const [projectToDelete, setProjectToDelete] = useState<ProjectSummary | null>(null)

  const handleProjectCreated = (projectName: string) => {
    onSelectProject(projectName)
    setIsOpen(false)
  }

  const handleDeleteClick = (e: React.MouseEvent, project: ProjectSummary) => {
    e.stopPropagation()
    setProjectToDelete(project)
    setIsOpen(false)
  }

  const handleProjectDeleted = () => {
    // If we deleted the currently selected project, clear selection
    if (projectToDelete?.name === selectedProject) {
      onSelectProject(null)
    }
    setProjectToDelete(null)
  }

import { useState } from 'react'
import { ChevronDown, Plus, FolderOpen, Loader2, Trash2 } from 'lucide-react'
import type { ProjectSummary } from '../lib/types'
import { NewProjectModal } from './NewProjectModal'
import { DeleteProjectModal } from './DeleteProjectModal'

interface ProjectSelectorProps {
  projects: ProjectSummary[]
  selectedProject: string | null
  onSelectProject: (name: string | null) => void
  isLoading: boolean
}

export function ProjectSelector({
  projects,
  selectedProject,
  onSelectProject,
  isLoading,
}: ProjectSelectorProps) {
  const [isOpen, setIsOpen] = useState(false)
  const [showNewProjectModal, setShowNewProjectModal] = useState(false)

  const handleProjectCreated = (projectName: string) => {
    onSelectProject(projectName)
    setIsOpen(false)
  }

  const selectedProjectData = projects.find(p => p.name === selectedProject)

  return (

      {/* Dropdown Trigger */}
      <button
        onClick={() => setIsOpen(!isOpen)}
        className="neo-btn bg-white text-[var(--color-neo-text)] min-w-[200px] justify-between"
        disabled={isLoading}
      >
        {isLoading ? (
          <Loader2 size={18} className="animate-spin" />
        ) : selectedProject ? (
          <>

              <FolderOpen size={18} />
              {selectedProject}

            {selectedProjectData && selectedProjectData.stats.total > 0 && (

                {selectedProjectData.stats.percentage}%

            )}
          </>
        ) : (

            Select Project

        )}
        <ChevronDown size={18} className={`transition-transform ${isOpen ? 'rotate-180' : ''}`} />
      </button>

      {/* Dropdown Menu */}
      {isOpen && (
        <>
          {/* Backdrop */}

 setIsOpen(false)}
          />

          {/* Menu */}

            {projects.length > 0 ? (

                {projects.map(project => (
                  <button
                    key={project.name}
                    onClick={() => {
                      onSelectProject(project.name)
                      setIsOpen(false)
                    }}
                    className={`w-full neo-dropdown-item flex items-center justify-between ${
                      project.name === selectedProject
                        ? 'bg-[var(--color-neo-pending)]'
                        : ''
                    }`}
                  >

                      <FolderOpen size={16} />
                      {project.name}

                    {project.stats.total > 0 && (

                        {project.stats.passing}/{project.stats.total}

                    )}
                  </button>
                ))}

            ) : (

                No projects yet

            )}

            {/* Divider */}

            {/* Create New */}
            <button
              onClick={() => {
                setShowNewProjectModal(true)
                setIsOpen(false)
              }}
              className="w-full neo-dropdown-item flex items-center gap-2 font-bold"
            >
              <Plus size={16} />
              New Project
            </button>

        </>
      )}

      {/* New Project Modal */}
      <NewProjectModal
        isOpen={showNewProjectModal}
        onClose={() => setShowNewProjectModal(false)}
        onProjectCreated={handleProjectCreated}
      />

  )
}

+  const [projectToDelete, setProjectToDelete] = useState<ProjectSummary | null>(null)

+  const handleDeleteClick = (e: React.MouseEvent, project: ProjectSummary) => {

+    // If we deleted the currently selected project, clear selection

+    if (projectToDelete?.name === selectedProject) {


Now I need to add the delete button to each project item in the dropdown and add the DeleteProjectModal.


{projects.map(project => (
                  <button
                    key={project.name}
                    onClick={() => {
                      onSelectProject(project.name)
                      setIsOpen(false)
                    }}
                    className={`w-full neo-dropdown-item flex items-center justify-between ${
                      project.name === selectedProject
                        ? 'bg-[var(--color-neo-pending)]'
                        : ''
                    }`}
                  >

                      <FolderOpen size={16} />
                      {project.name}

                    {project.stats.total > 0 && (

                        {project.stats.passing}/{project.stats.total}

                    )}
                  </button>
                ))}

{projects.map(project => (

                    <button
                      onClick={() => {
                        onSelectProject(project.name)
                        setIsOpen(false)
                      }}
                      className="flex-1 flex items-center gap-2 text-left"
                    >
                      <FolderOpen size={16} />
                      {project.name}
                    </button>

                      {project.stats.total > 0 && (

                          {project.stats.passing}/{project.stats.total}

                      )}
                      <button
                        onClick={(e) => handleDeleteClick(e, project)}
                        className="p-1 opacity-0 group-hover:opacity-100 hover:bg-red-100 rounded transition-opacity"
                        title="Delete project"
                      >
                        <Trash2 size={14} className="text-red-500" />
                      </button>

                ))}


{projects.map(project => (

                    <button
                      onClick={() => {
                        onSelectProject(project.name)
                        setIsOpen(false)
                      }}
                      className="flex-1 flex items-center gap-2 text-left"
                    >
                      <FolderOpen size={16} />
                      {project.name}
                    </button>

                      {project.stats.total > 0 && (

                          {project.stats.passing}/{project.stats.total}

                      )}
                      <button
                        onClick={(e) => handleDeleteClick(e, project)}
                        className="p-1 opacity-0 group-hover:opacity-100 hover:bg-red-100 rounded transition-opacity"
                        title="Delete project"
                      >
                        <Trash2 size={14} className="text-red-500" />
                      </button>

                ))}

import { useState } from 'react'
import { ChevronDown, Plus, FolderOpen, Loader2, Trash2 } from 'lucide-react'
import type { ProjectSummary } from '../lib/types'
import { NewProjectModal } from './NewProjectModal'
import { DeleteProjectModal } from './DeleteProjectModal'

interface ProjectSelectorProps {
  projects: ProjectSummary[]
  selectedProject: string | null
  onSelectProject: (name: string | null) => void
  isLoading: boolean
}

export function ProjectSelector({
  projects,
  selectedProject,
  onSelectProject,
  isLoading,
}: ProjectSelectorProps) {
  const [isOpen, setIsOpen] = useState(false)
  const [showNewProjectModal, setShowNewProjectModal] = useState(false)
  const [projectToDelete, setProjectToDelete] = useState<ProjectSummary | null>(null)

  const handleProjectCreated = (projectName: string) => {
    onSelectProject(projectName)
    setIsOpen(false)
  }

  const handleDeleteClick = (e: React.MouseEvent, project: ProjectSummary) => {
    e.stopPropagation()
    setProjectToDelete(project)
    setIsOpen(false)
  }

  const handleProjectDeleted = () => {
    // If we deleted the currently selected project, clear selection
    if (projectToDelete?.name === selectedProject) {
      onSelectProject(null)
    }
    setProjectToDelete(null)
  }

  const selectedProjectData = projects.find(p => p.name === selectedProject)

  return (

      {/* Dropdown Trigger */}
      <button
        onClick={() => setIsOpen(!isOpen)}
        className="neo-btn bg-white text-[var(--color-neo-text)] min-w-[200px] justify-between"
        disabled={isLoading}
      >
        {isLoading ? (
          <Loader2 size={18} className="animate-spin" />
        ) : selectedProject ? (
          <>

              <FolderOpen size={18} />
              {selectedProject}

            {selectedProjectData && selectedProjectData.stats.total > 0 && (

                {selectedProjectData.stats.percentage}%

            )}
          </>
        ) : (

            Select Project

        )}
        <ChevronDown size={18} className={`transition-transform ${isOpen ? 'rotate-180' : ''}`} />
      </button>

      {/* Dropdown Menu */}
      {isOpen && (
        <>
          {/* Backdrop */}

 setIsOpen(false)}
          />

          {/* Menu */}

            {projects.length > 0 ? (

                {projects.map(project => (
                  <button
                    key={project.name}
                    onClick={() => {
                      onSelectProject(project.name)
                      setIsOpen(false)
                    }}
                    className={`w-full neo-dropdown-item flex items-center justify-between ${
                      project.name === selectedProject
                        ? 'bg-[var(--color-neo-pending)]'
                        : ''
                    }`}
                  >

                      <FolderOpen size={16} />
                      {project.name}

                    {project.stats.total > 0 && (

                        {project.stats.passing}/{project.stats.total}

                    )}
                  </button>
                ))}

            ) : (

                No projects yet

            )}

            {/* Divider */}

            {/* Create New */}
            <button
              onClick={() => {
                setShowNewProjectModal(true)
                setIsOpen(false)
              }}
              className="w-full neo-dropdown-item flex items-center gap-2 font-bold"
            >
              <Plus size={16} />
              New Project
            </button>

        </>
      )}

      {/* New Project Modal */}
      <NewProjectModal
        isOpen={showNewProjectModal}
        onClose={() => setShowNewProjectModal(false)}
        onProjectCreated={handleProjectCreated}
      />

  )
}

-                      onSelectProject(project.name)

-                    className={`w-full neo-dropdown-item flex items-center justify-between ${

+                    className={`w-full neo-dropdown-item flex items-center justify-between group ${

-                    <span className="flex items-center gap-2">

+                        onSelectProject(project.name)

+                        setIsOpen(false)

+                      className="flex-1 flex items-center gap-2 text-left"

-                    {project.stats.total > 0 && (

-                      <span className="text-sm font-mono">

-                        {project.stats.passing}/{project.stats.total}

+                    <div className="flex items-center gap-2">

+                      {project.stats.total > 0 && (

+                        <span className="text-sm font-mono">

+                          {project.stats.passing}/{project.stats.total}

+                        onClick={(e) => handleDeleteClick(e, project)}

+                        className="p-1 opacity-0 group-hover:opacity-100 hover:bg-red-100 rounded transition-opacity"

+                        title="Delete project"

+                        <Trash2 size={14} className="text-red-500" />


Now I need to add the DeleteProjectModal at the end of the component.


      {/* New Project Modal */}
      <NewProjectModal
        isOpen={showNewProjectModal}
        onClose={() => setShowNewProjectModal(false)}
        onProjectCreated={handleProjectCreated}
      />
    </div>
  )
}

      {/* New Project Modal */}
      <NewProjectModal
        isOpen={showNewProjectModal}
        onClose={() => setShowNewProjectModal(false)}
        onProjectCreated={handleProjectCreated}
      />

      {/* Delete Project Modal */}
      {projectToDelete && (
        <DeleteProjectModal
          project={projectToDelete}
          isOpen={!!projectToDelete}
          onClose={() => setProjectToDelete(null)}
          onDeleted={handleProjectDeleted}
        />
      )}
    </div>
  )
}


      {/* New Project Modal */}
      <NewProjectModal
        isOpen={showNewProjectModal}
        onClose={() => setShowNewProjectModal(false)}
        onProjectCreated={handleProjectCreated}
      />
    </div>
  )
}

      {/* New Project Modal */}
      <NewProjectModal
        isOpen={showNewProjectModal}
        onClose={() => setShowNewProjectModal(false)}
        onProjectCreated={handleProjectCreated}
      />

      {/* Delete Project Modal */}
      {projectToDelete && (
        <DeleteProjectModal
          project={projectToDelete}
          isOpen={!!projectToDelete}
          onClose={() => setProjectToDelete(null)}
          onDeleted={handleProjectDeleted}
        />
      )}
    </div>
  )
}

import { useState } from 'react'
import { ChevronDown, Plus, FolderOpen, Loader2, Trash2 } from 'lucide-react'
import type { ProjectSummary } from '../lib/types'
import { NewProjectModal } from './NewProjectModal'
import { DeleteProjectModal } from './DeleteProjectModal'

interface ProjectSelectorProps {
  projects: ProjectSummary[]
  selectedProject: string | null
  onSelectProject: (name: string | null) => void
  isLoading: boolean
}

export function ProjectSelector({
  projects,
  selectedProject,
  onSelectProject,
  isLoading,
}: ProjectSelectorProps) {
  const [isOpen, setIsOpen] = useState(false)
  const [showNewProjectModal, setShowNewProjectModal] = useState(false)
  const [projectToDelete, setProjectToDelete] = useState<ProjectSummary | null>(null)

  const handleProjectCreated = (projectName: string) => {
    onSelectProject(projectName)
    setIsOpen(false)
  }

  const handleDeleteClick = (e: React.MouseEvent, project: ProjectSummary) => {
    e.stopPropagation()
    setProjectToDelete(project)
    setIsOpen(false)
  }

  const handleProjectDeleted = () => {
    // If we deleted the currently selected project, clear selection
    if (projectToDelete?.name === selectedProject) {
      onSelectProject(null)
    }
    setProjectToDelete(null)
  }

  const selectedProjectData = projects.find(p => p.name === selectedProject)

  return (

      {/* Dropdown Trigger */}
      <button
        onClick={() => setIsOpen(!isOpen)}
        className="neo-btn bg-white text-[var(--color-neo-text)] min-w-[200px] justify-between"
        disabled={isLoading}
      >
        {isLoading ? (
          <Loader2 size={18} className="animate-spin" />
        ) : selectedProject ? (
          <>

              <FolderOpen size={18} />
              {selectedProject}

            {selectedProjectData && selectedProjectData.stats.total > 0 && (

                {selectedProjectData.stats.percentage}%

            )}
          </>
        ) : (

            Select Project

        )}
        <ChevronDown size={18} className={`transition-transform ${isOpen ? 'rotate-180' : ''}`} />
      </button>

      {/* Dropdown Menu */}
      {isOpen && (
        <>
          {/* Backdrop */}

 setIsOpen(false)}
          />

          {/* Menu */}

            {projects.length > 0 ? (

                {projects.map(project => (

                    <button
                      onClick={() => {
                        onSelectProject(project.name)
                        setIsOpen(false)
                      }}
                      className="flex-1 flex items-center gap-2 text-left"
                    >
                      <FolderOpen size={16} />
                      {project.name}
                    </button>

                      {project.stats.total > 0 && (

                          {project.stats.passing}/{project.stats.total}

                      )}
                      <button
                        onClick={(e) => handleDeleteClick(e, project)}
                        className="p-1 opacity-0 group-hover:opacity-100 hover:bg-red-100 rounded transition-opacity"
                        title="Delete project"
                      >
                        <Trash2 size={14} className="text-red-500" />
                      </button>

                ))}

            ) : (

                No projects yet

            )}

            {/* Divider */}

            {/* Create New */}
            <button
              onClick={() => {
                setShowNewProjectModal(true)
                setIsOpen(false)
              }}
              className="w-full neo-dropdown-item flex items-center gap-2 font-bold"
            >
              <Plus size={16} />
              New Project
            </button>

        </>
      )}

      {/* New Project Modal */}
      <NewProjectModal
        isOpen={showNewProjectModal}
        onClose={() => setShowNewProjectModal(false)}
        onProjectCreated={handleProjectCreated}
      />

  )
}

+          onClose={() => setProjectToDelete(null)}

+          onDeleted={handleProjectDeleted}


Now let me build the project to verify everything compiles correctly.


Now let me build to verify everything works:


Bash

cd "C:\Users\MikeSwanson\claude-projects\Autocode-remix\Autocoder-2.0\ui" && npm run build

Build UI to verify TypeScript compiles


> autocoder@1.0.0 build
> tsc -b && vite build

vite v5.4.21 building for production...
transforming...
✓ 1817 modules transformed.
rendering chunks...
computing gzip size...
dist/index.html                   0.79 kB │ gzip:   0.42 kB
dist/assets/index-ClmyDkZa.css   37.16 kB │ gzip:   7.55 kB
dist/assets/index-CbLmfRft.js   452.73 kB │ gzip: 130.28 kB
✓ built in 4.06s


The build succeeded. The delete project feature is now complete. Let me provide a summary to the user.


Done! The project deletion feature is now implemented:

**Changes Made:**

1. **`ui/src/lib/api.ts`** - Updated `deleteProject` function to accept `deleteFiles` parameter

2. **`ui/src/hooks/useProjects.ts`** - Updated `useDeleteProject` mutation to pass `deleteFiles` option

3. **`ui/src/components/DeleteProjectModal.tsx`** (new) - Confirmation dialog with:
   - Project info display (path, feature progress)
   - Checkbox to also delete files from disk
   - When deleting files: requires typing project name to confirm
   - Warning about irreversibility

4. **`ui/src/components/ProjectSelector.tsx`** - Added:
   - Trash icon on hover for each project in dropdown
   - Integration with DeleteProjectModal

**How it works:**
- Hover over a project in the dropdown to reveal the delete button (trash icon)
- Click to open the confirmation modal
- Choose whether to only remove from registry or also delete files
- If deleting files, must type project name to confirm

The backend already had the `DELETE /api/projects/{name}?delete_files=true` endpoint, so this was just the UI work.


Can we set destinations for projects?


The user is asking about setting destinations for projects. This could mean:
1. A default location where new projects are created
2. Being able to move/relocate existing projects
3. Setting a default "projects root" directory

Let me clarify what they mean, but I think they likely want a default projects directory - so when creating a new project, it would default to that location rather than having to browse every time.

Let me ask for clarification.


Could you clarify what you mean by "destinations"? A few possibilities:

1. **Default projects folder** - Set a default directory where new projects are created (e.g., `C:\Projects`), so you don't have to browse every time

2. **Project categories/groups** - Organize projects into different folders by type (e.g., `~/work/`, `~/personal/`, `~/experiments/`)

3. **Move/relocate projects** - Ability to move an existing project to a new location and update the registry

Which of these are you looking for, or something else?


How about the ability to add a reference folder (previous project code) for consideration.


The user wants to add reference folders - essentially pointing to existing codebases that the agent can reference when working on a project. This is useful for:
- Learning patterns from existing code
- Copying code/patterns from a previous project
- Understanding how similar features were implemented elsewhere

This would need:
1. Backend: Store reference folders per project (in the registry or a separate table)
2. Backend: API endpoints to add/remove/list reference folders
3. Agent: Inject reference folder context into prompts so the agent knows about them
4. UI: Interface to manage reference folders per project

Let me plan this out and implement it.


That's a great idea - reference folders for the agent to learn from. Let me implement this:


TodoWrite

Add reference_folders table to registry database

Adding reference_folders table to registry

Create API endpoints for reference folder management

Creating API endpoints for reference folders

Inject reference folders into agent prompts

Injecting reference folders into agent prompts

Create UI for managing reference folders

Creating UI for reference folder management

Build and test


Todos have been modified successfully. Ensure that you continue to use the todo list to track your progress. Please proceed with the current tasks if applicable

Add reference_folders table to registry database

Adding reference_folders table to registry

Create API endpoints for reference folder management

Creating API endpoints for reference folders

Inject reference folders into agent prompts

Injecting reference folders into agent prompts

Create UI for managing reference folders

Creating UI for reference folder management

Build and test


Now let me implement this. I'll start by adding the reference folders support to the registry, then add API endpoints, update prompts, and create the UI.


    50→# SQLAlchemy Model
    51→# =============================================================================
    52→
    53→Base = declarative_base()
    54→
    55→
    56→class Project(Base):
    57→    """SQLAlchemy model for registered projects."""
    58→    __tablename__ = "projects"
    59→
    60→    name = Column(String(50), primary_key=True, index=True)
    61→    path = Column(String, nullable=False)  # POSIX format for cross-platform
    62→    created_at = Column(DateTime, nullable=False)
    63→
    64→
    65→# =============================================================================
    66→# Database Connection
    67→# =============================================================================
    68→
    69→# Module-level singleton for database engine
    70→_engine = None
    71→_SessionLocal = None
    72→
    73→
    74→def get_config_dir() -> Path:
    75→    """
    76→    Get the config directory: ~/.autocoder/
    77→
    78→    Returns:
    79→        Path to ~/.autocoder/ (created if it doesn't exist)
    80→    """
    81→    config_dir = Path.home() / ".autocoder"
    82→    config_dir.mkdir(parents=True, exist_ok=True)
    83→    return config_dir
    84→
    85→
    86→def get_registry_path() -> Path:
    87→    """Get the path to the registry database."""
    88→    return get_config_dir() / "registry.db"
    89→
    90→
    91→def _get_engine():
    92→    """
    93→    Get or create the database engine (singleton pattern).
    94→
    95→    Returns:
    96→        Tuple of (engine, SessionLocal)
    97→    """
    98→    global _engine, _SessionLocal
    99→
   100→    if _engine is None:
   101→        db_path = get_registry_path()
   102→        db_url = f"sqlite:///{db_path.as_posix()}"
   103→        _engine = create_engine(db_url, connect_args={"check_same_thread": False})
   104→        Base.metadata.create_all(bind=_engine)
   105→        _SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=_engine)
   106→        logger.debug("Initialized registry database at: %s", db_path)
   107→
   108→    return _engine, _SessionLocal
   109→
   110→
   111→@contextmanager
   112→def _get_session():
   113→    """
   114→    Context manager for database sessions with automatic commit/rollback.
   115→
   116→    Yields:
   117→        SQLAlchemy session
   118→    """
   119→    _, SessionLocal = _get_engine()
   120→    session = SessionLocal()
   121→    try:
   122→        yield session
   123→        session.commit()
   124→    except Exception:
   125→        session.rollback()
   126→        raise
   127→    finally:
   128→        session.close()
   129→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

# SQLAlchemy Model
# =============================================================================

Base = declarative_base()

class Project(Base):
    """SQLAlchemy model for registered projects."""
    __tablename__ = "projects"

    name = Column(String(50), primary_key=True, index=True)
    path = Column(String, nullable=False)  # POSIX format for cross-platform
    created_at = Column(DateTime, nullable=False)

# =============================================================================
# Database Connection
# =============================================================================

# Module-level singleton for database engine
_engine = None
_SessionLocal = None

def get_config_dir() -> Path:
    """
    Get the config directory: ~/.autocoder/

    Returns:
        Path to ~/.autocoder/ (created if it doesn't exist)
    """
    config_dir = Path.home() / ".autocoder"
    config_dir.mkdir(parents=True, exist_ok=True)
    return config_dir

def get_registry_path() -> Path:
    """Get the path to the registry database."""
    return get_config_dir() / "registry.db"

def _get_engine():
    """
    Get or create the database engine (singleton pattern).

    Returns:
        Tuple of (engine, SessionLocal)
    """
    global _engine, _SessionLocal

    if _engine is None:
        db_path = get_registry_path()
        db_url = f"sqlite:///{db_path.as_posix()}"
        _engine = create_engine(db_url, connect_args={"check_same_thread": False})
        Base.metadata.create_all(bind=_engine)
        _SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=_engine)
        logger.debug("Initialized registry database at: %s", db_path)

    return _engine, _SessionLocal

@contextmanager
def _get_session():
    """
    Context manager for database sessions with automatic commit/rollback.

    Yields:
        SQLAlchemy session
    """
    _, SessionLocal = _get_engine()
    session = SessionLocal()
    try:
        yield session
        session.commit()
    except Exception:
        session.rollback()
        raise
    finally:
        session.close()


I see the structure. I need to add a new model for reference folders. Let me add:
1. A ReferenceFolder model that links to projects
2. CRUD functions for reference folders

Let me update the registry.py file to add this.


Edit

from sqlalchemy import Column, DateTime, String, create_engine

from sqlalchemy import Column, DateTime, Integer, String, Text, ForeignKey, create_engine


The file C:\Users\MikeSwanson\claude-projects\Autocode-remix\Autocoder-2.0\registry.py has been updated successfully.

"""
Project Registry Module
=======================

Cross-platform project registry for storing project name to path mappings.
Uses SQLite database stored at ~/.autocoder/registry.db.
"""

import logging
import os
import re
from contextlib import contextmanager
from datetime import datetime
from pathlib import Path
from typing import Any

from sqlalchemy import Column, DateTime, String, create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

# Module logger
logger = logging.getLogger(__name__)

# =============================================================================
# Exceptions
# =============================================================================

class RegistryError(Exception):
    """Base registry exception."""
    pass

class RegistryNotFound(RegistryError):
    """Registry file doesn't exist."""
    pass

class RegistryCorrupted(RegistryError):
    """Registry database is corrupted."""
    pass

class RegistryPermissionDenied(RegistryError):
    """Can't read/write registry file."""
    pass

# =============================================================================
# SQLAlchemy Model
# =============================================================================

Base = declarative_base()

class Project(Base):
    """SQLAlchemy model for registered projects."""
    __tablename__ = "projects"

    name = Column(String(50), primary_key=True, index=True)
    path = Column(String, nullable=False)  # POSIX format for cross-platform
    created_at = Column(DateTime, nullable=False)

# =============================================================================
# Database Connection
# =============================================================================

# Module-level singleton for database engine
_engine = None
_SessionLocal = None

def get_config_dir() -> Path:
    """
    Get the config directory: ~/.autocoder/

    Returns:
        Path to ~/.autocoder/ (created if it doesn't exist)
    """
    config_dir = Path.home() / ".autocoder"
    config_dir.mkdir(parents=True, exist_ok=True)
    return config_dir

def get_registry_path() -> Path:
    """Get the path to the registry database."""
    return get_config_dir() / "registry.db"

def _get_engine():
    """
    Get or create the database engine (singleton pattern).

    Returns:
        Tuple of (engine, SessionLocal)
    """
    global _engine, _SessionLocal

    if _engine is None:
        db_path = get_registry_path()
        db_url = f"sqlite:///{db_path.as_posix()}"
        _engine = create_engine(db_url, connect_args={"check_same_thread": False})
        Base.metadata.create_all(bind=_engine)
        _SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=_engine)
        logger.debug("Initialized registry database at: %s", db_path)

    return _engine, _SessionLocal

@contextmanager
def _get_session():
    """
    Context manager for database sessions with automatic commit/rollback.

    Yields:
        SQLAlchemy session
    """
    _, SessionLocal = _get_engine()
    session = SessionLocal()
    try:
        yield session
        session.commit()
    except Exception:
        session.rollback()
        raise
    finally:
        session.close()

# =============================================================================
# Project CRUD Functions
# =============================================================================

def register_project(name: str, path: Path) -> None:
    """
    Register a new project in the registry.

    Args:
        name: The project name (unique identifier).
        path: The absolute path to the project directory.

    Raises:
        ValueError: If project name is invalid or path is not absolute.
        RegistryError: If a project with that name already exists.
    """
    # Validate name
    if not re.match(r'^[a-zA-Z0-9_-]{1,50}$', name):
        raise ValueError(
            "Invalid project name. Use only letters, numbers, hyphens, "
            "and underscores (1-50 chars)."
        )

    # Ensure path is absolute
    path = Path(path).resolve()

    with _get_session() as session:
        existing = session.query(Project).filter(Project.name == name).first()
        if existing:
            logger.warning("Attempted to register duplicate project: %s", name)
            raise RegistryError(f"Project '{name}' already exists in registry")

        project = Project(
            name=name,
            path=path.as_posix(),
            created_at=datetime.now()
        )
        session.add(project)

    logger.info("Registered project '%s' at path: %s", name, path)

def unregister_project(name: str) -> bool:
    """
    Remove a project from the registry.

    Args:
        name: The project name to remove.

    Returns:
        True if removed, False if project wasn't found.
    """
    with _get_session() as session:
        project = session.query(Project).filter(Project.name == name).first()
        if not project:
            logger.debug("Attempted to unregister non-existent project: %s", name)
            return False

        session.delete(project)

    logger.info("Unregistered project: %s", name)
    return True

def get_project_path(name: str) -> Path | None:
    """
    Look up a project's path by name.

    Args:
        name: The project name.

    Returns:
        The project Path, or None if not found.
    """
    _, SessionLocal = _get_engine()
    session = SessionLocal()
    try:
        project = session.query(Project).filter(Project.name == name).first()
        if project is None:
            return None
        return Path(project.path)
    finally:
        session.close()

def list_registered_projects() -> dict[str, dict[str, Any]]:
    """
    Get all registered projects.

    Returns:
        Dictionary mapping project names to their info dictionaries.
    """
    _, SessionLocal = _get_engine()
    session = SessionLocal()
    try:
        projects = session.query(Project).all()
        return {
            p.name: {
                "path": p.path,
                "created_at": p.created_at.isoformat() if p.created_at else None
            }
            for p in projects
        }
    finally:
        session.close()

def get_project_info(name: str) -> dict[str, Any] | None:
    """
    Get full info about a project.

    Args:
        name: The project name.

    Returns:
        Project info dictionary, or None if not found.
    """
    _, SessionLocal = _get_engine()
    session = SessionLocal()
    try:
        project = session.query(Project).filter(Project.name == name).first()
        if project is None:
            return None
        return {
            "path": project.path,
            "created_at": project.created_at.isoformat() if project.created_at else None
        }
    finally:
        session.close()

def update_project_path(name: str, new_path: Path) -> bool:
    """
    Update a project's path (for relocating projects).

    Args:
        name: The project name.
        new_path: The new absolute path.

    Returns:
        True if updated, False if project wasn't found.
    """
    new_path = Path(new_path).resolve()

    with _get_session() as session:
        project = session.query(Project).filter(Project.name == name).first()
        if not project:
            return False

        project.path = new_path.as_posix()

    return True

# =============================================================================
# Validation Functions
# =============================================================================

def validate_project_path(path: Path) -> tuple[bool, str]:
    """
    Validate that a project path is accessible and writable.

    Args:
        path: The path to validate.

    Returns:
        Tuple of (is_valid, error_message).
    """
    path = Path(path).resolve()

    # Check if path exists
    if not path.exists():
        return False, f"Path does not exist: {path}"

    # Check if it's a directory
    if not path.is_dir():
        return False, f"Path is not a directory: {path}"

    # Check read permissions
    if not os.access(path, os.R_OK):
        return False, f"No read permission: {path}"

    # Check write permissions
    if not os.access(path, os.W_OK):
        return False, f"No write permission: {path}"

    return True, ""

def cleanup_stale_projects() -> list[str]:
    """
    Remove projects from registry whose paths no longer exist.

    Returns:
        List of removed project names.
    """
    removed = []

    with _get_session() as session:
        projects = session.query(Project).all()
        for project in projects:
            path = Path(project.path)
            if not path.exists():
                session.delete(project)
                removed.append(project.name)

    if removed:
        logger.info("Cleaned up stale projects: %s", removed)

    return removed

def list_valid_projects() -> list[dict[str, Any]]:
    """
    List all projects that have valid, accessible paths.

    Returns:
        List of project info dicts with additional 'name' field.
    """
    _, SessionLocal = _get_engine()
    session = SessionLocal()
    try:
        projects = session.query(Project).all()
        valid = []
        for p in projects:
            path = Path(p.path)
            is_valid, _ = validate_project_path(path)
            if is_valid:
                valid.append({
                    "name": p.name,
                    "path": p.path,
                    "created_at": p.created_at.isoformat() if p.created_at else None
                })
        return valid
    finally:
        session.close()

-from sqlalchemy import Column, DateTime, String, create_engine

+from sqlalchemy import Column, DateTime, Integer, String, Text, ForeignKey, create_engine


Now I need to add the ReferenceFolder model and the CRUD functions for it.


class Project(Base):
    """SQLAlchemy model for registered projects."""
    __tablename__ = "projects"

    name = Column(String(50), primary_key=True, index=True)
    path = Column(String, nullable=False)  # POSIX format for cross-platform
    created_at = Column(DateTime, nullable=False)

class Project(Base):
    """SQLAlchemy model for registered projects."""
    __tablename__ = "projects"

    name = Column(String(50), primary_key=True, index=True)
    path = Column(String, nullable=False)  # POSIX format for cross-platform
    created_at = Column(DateTime, nullable=False)

class ReferenceFolder(Base):
    """SQLAlchemy model for reference folders linked to projects."""
    __tablename__ = "reference_folders"

    id = Column(Integer, primary_key=True, autoincrement=True)
    project_name = Column(String(50), ForeignKey("projects.name", ondelete="CASCADE"), nullable=False, index=True)
    path = Column(String, nullable=False)  # POSIX format
    alias = Column(String(100), nullable=True)  # Optional friendly name
    description = Column(Text, nullable=True)  # What this reference is for
    created_at = Column(DateTime, nullable=False)


class Project(Base):
    """SQLAlchemy model for registered projects."""
    __tablename__ = "projects"

    name = Column(String(50), primary_key=True, index=True)
    path = Column(String, nullable=False)  # POSIX format for cross-platform
    created_at = Column(DateTime, nullable=False)

class ReferenceFolder(Base):
    """SQLAlchemy model for reference folders linked to projects."""
    __tablename__ = "reference_folders"

    id = Column(Integer, primary_key=True, autoincrement=True)
    project_name = Column(String(50), ForeignKey("projects.name", ondelete="CASCADE"), nullable=False, index=True)
    path = Column(String, nullable=False)  # POSIX format
    alias = Column(String(100), nullable=True)  # Optional friendly name
    description = Column(Text, nullable=True)  # What this reference is for
    created_at = Column(DateTime, nullable=False)

"""
Project Registry Module
=======================

Cross-platform project registry for storing project name to path mappings.
Uses SQLite database stored at ~/.autocoder/registry.db.
"""

import logging
import os
import re
from contextlib import contextmanager
from datetime import datetime
from pathlib import Path
from typing import Any

from sqlalchemy import Column, DateTime, Integer, String, Text, ForeignKey, create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

# Module logger
logger = logging.getLogger(__name__)

# =============================================================================
# Exceptions
# =============================================================================

class RegistryError(Exception):
    """Base registry exception."""
    pass

class RegistryNotFound(RegistryError):
    """Registry file doesn't exist."""
    pass

class RegistryCorrupted(RegistryError):
    """Registry database is corrupted."""
    pass

class RegistryPermissionDenied(RegistryError):
    """Can't read/write registry file."""
    pass

# =============================================================================
# SQLAlchemy Model
# =============================================================================

Base = declarative_base()

class Project(Base):
    """SQLAlchemy model for registered projects."""
    __tablename__ = "projects"

    name = Column(String(50), primary_key=True, index=True)
    path = Column(String, nullable=False)  # POSIX format for cross-platform
    created_at = Column(DateTime, nullable=False)

# =============================================================================
# Database Connection
# =============================================================================

# Module-level singleton for database engine
_engine = None
_SessionLocal = None

def get_config_dir() -> Path:
    """
    Get the config directory: ~/.autocoder/

    Returns:
        Path to ~/.autocoder/ (created if it doesn't exist)
    """
    config_dir = Path.home() / ".autocoder"
    config_dir.mkdir(parents=True, exist_ok=True)
    return config_dir

def get_registry_path() -> Path:
    """Get the path to the registry database."""
    return get_config_dir() / "registry.db"

def _get_engine():
    """
    Get or create the database engine (singleton pattern).

    Returns:
        Tuple of (engine, SessionLocal)
    """
    global _engine, _SessionLocal

    if _engine is None:
        db_path = get_registry_path()
        db_url = f"sqlite:///{db_path.as_posix()}"
        _engine = create_engine(db_url, connect_args={"check_same_thread": False})
        Base.metadata.create_all(bind=_engine)
        _SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=_engine)
        logger.debug("Initialized registry database at: %s", db_path)

    return _engine, _SessionLocal

@contextmanager
def _get_session():
    """
    Context manager for database sessions with automatic commit/rollback.

    Yields:
        SQLAlchemy session
    """
    _, SessionLocal = _get_engine()
    session = SessionLocal()
    try:
        yield session
        session.commit()
    except Exception:
        session.rollback()
        raise
    finally:
        session.close()

# =============================================================================
# Project CRUD Functions
# =============================================================================

def register_project(name: str, path: Path) -> None:
    """
    Register a new project in the registry.

    Args:
        name: The project name (unique identifier).
        path: The absolute path to the project directory.

    Raises:
        ValueError: If project name is invalid or path is not absolute.
        RegistryError: If a project with that name already exists.
    """
    # Validate name
    if not re.match(r'^[a-zA-Z0-9_-]{1,50}$', name):
        raise ValueError(
            "Invalid project name. Use only letters, numbers, hyphens, "
            "and underscores (1-50 chars)."
        )

    # Ensure path is absolute
    path = Path(path).resolve()

    with _get_session() as session:
        existing = session.query(Project).filter(Project.name == name).first()
        if existing:
            logger.warning("Attempted to register duplicate project: %s", name)
            raise RegistryError(f"Project '{name}' already exists in registry")

        project = Project(
            name=name,
            path=path.as_posix(),
            created_at=datetime.now()
        )
        session.add(project)

    logger.info("Registered project '%s' at path: %s", name, path)

def unregister_project(name: str) -> bool:
    """
    Remove a project from the registry.

    Args:
        name: The project name to remove.

    Returns:
        True if removed, False if project wasn't found.
    """
    with _get_session() as session:
        project = session.query(Project).filter(Project.name == name).first()
        if not project:
            logger.debug("Attempted to unregister non-existent project: %s", name)
            return False

        session.delete(project)

    logger.info("Unregistered project: %s", name)
    return True

def get_project_path(name: str) -> Path | None:
    """
    Look up a project's path by name.

    Args:
        name: The project name.

    Returns:
        The project Path, or None if not found.
    """
    _, SessionLocal = _get_engine()
    session = SessionLocal()
    try:
        project = session.query(Project).filter(Project.name == name).first()
        if project is None:
            return None
        return Path(project.path)
    finally:
        session.close()

def list_registered_projects() -> dict[str, dict[str, Any]]:
    """
    Get all registered projects.

    Returns:
        Dictionary mapping project names to their info dictionaries.
    """
    _, SessionLocal = _get_engine()
    session = SessionLocal()
    try:
        projects = session.query(Project).all()
        return {
            p.name: {
                "path": p.path,
                "created_at": p.created_at.isoformat() if p.created_at else None
            }
            for p in projects
        }
    finally:
        session.close()

def get_project_info(name: str) -> dict[str, Any] | None:
    """
    Get full info about a project.

    Args:
        name: The project name.

    Returns:
        Project info dictionary, or None if not found.
    """
    _, SessionLocal = _get_engine()
    session = SessionLocal()
    try:
        project = session.query(Project).filter(Project.name == name).first()
        if project is None:
            return None
        return {
            "path": project.path,
            "created_at": project.created_at.isoformat() if project.created_at else None
        }
    finally:
        session.close()

def update_project_path(name: str, new_path: Path) -> bool:
    """
    Update a project's path (for relocating projects).

    Args:
        name: The project name.
        new_path: The new absolute path.

    Returns:
        True if updated, False if project wasn't found.
    """
    new_path = Path(new_path).resolve()

    with _get_session() as session:
        project = session.query(Project).filter(Project.name == name).first()
        if not project:
            return False

        project.path = new_path.as_posix()

    return True

# =============================================================================
# Validation Functions
# =============================================================================

def validate_project_path(path: Path) -> tuple[bool, str]:
    """
    Validate that a project path is accessible and writable.

    Args:
        path: The path to validate.

    Returns:
        Tuple of (is_valid, error_message).
    """
    path = Path(path).resolve()

    # Check if path exists
    if not path.exists():
        return False, f"Path does not exist: {path}"

    # Check if it's a directory
    if not path.is_dir():
        return False, f"Path is not a directory: {path}"

    # Check read permissions
    if not os.access(path, os.R_OK):
        return False, f"No read permission: {path}"

    # Check write permissions
    if not os.access(path, os.W_OK):
        return False, f"No write permission: {path}"

    return True, ""

def cleanup_stale_projects() -> list[str]:
    """
    Remove projects from registry whose paths no longer exist.

    Returns:
        List of removed project names.
    """
    removed = []

    with _get_session() as session:
        projects = session.query(Project).all()
        for project in projects:
            path = Path(project.path)
            if not path.exists():
                session.delete(project)
                removed.append(project.name)

    if removed:
        logger.info("Cleaned up stale projects: %s", removed)

    return removed

def list_valid_projects() -> list[dict[str, Any]]:
    """
    List all projects that have valid, accessible paths.

    Returns:
        List of project info dicts with additional 'name' field.
    """
    _, SessionLocal = _get_engine()
    session = SessionLocal()
    try:
        projects = session.query(Project).all()
        valid = []
        for p in projects:
            path = Path(p.path)
            is_valid, _ = validate_project_path(path)
            if is_valid:
                valid.append({
                    "name": p.name,
                    "path": p.path,
                    "created_at": p.created_at.isoformat() if p.created_at else None
                })
        return valid
    finally:
        session.close()

+    """SQLAlchemy model for reference folders linked to projects."""

+    __tablename__ = "reference_folders"

+    id = Column(Integer, primary_key=True, autoincrement=True)

+    project_name = Column(String(50), ForeignKey("projects.name", ondelete="CASCADE"), nullable=False, index=True)

+    path = Column(String, nullable=False)  # POSIX format

+    alias = Column(String(100), nullable=True)  # Optional friendly name

+    description = Column(Text, nullable=True)  # What this reference is for

+    created_at = Column(DateTime, nullable=False)


Now I need to add CRUD functions for reference folders at the end of the registry.py file.


   340→
   341→    with _get_session() as session:
   342→        projects = session.query(Project).all()
   343→        for project in projects:
   344→            path = Path(project.path)
   345→            if not path.exists():
   346→                session.delete(project)
   347→                removed.append(project.name)
   348→
   349→    if removed:
   350→        logger.info("Cleaned up stale projects: %s", removed)
   351→
   352→    return removed
   353→
   354→
   355→def list_valid_projects() -> list[dict[str, Any]]:
   356→    """
   357→    List all projects that have valid, accessible paths.
   358→
   359→    Returns:
   360→        List of project info dicts with additional 'name' field.
   361→    """
   362→    _, SessionLocal = _get_engine()
   363→    session = SessionLocal()
   364→    try:
   365→        projects = session.query(Project).all()
   366→        valid = []
   367→        for p in projects:
   368→            path = Path(p.path)
   369→            is_valid, _ = validate_project_path(path)
   370→            if is_valid:
   371→                valid.append({
   372→                    "name": p.name,
   373→                    "path": p.path,
   374→                    "created_at": p.created_at.isoformat() if p.created_at else None
   375→                })
   376→        return valid
   377→    finally:
   378→        session.close()
   379→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

    with _get_session() as session:
        projects = session.query(Project).all()
        for project in projects:
            path = Path(project.path)
            if not path.exists():
                session.delete(project)
                removed.append(project.name)

    if removed:
        logger.info("Cleaned up stale projects: %s", removed)

    return removed

def list_valid_projects() -> list[dict[str, Any]]:
    """
    List all projects that have valid, accessible paths.

    Returns:
        List of project info dicts with additional 'name' field.
    """
    _, SessionLocal = _get_engine()
    session = SessionLocal()
    try:
        projects = session.query(Project).all()
        valid = []
        for p in projects:
            path = Path(p.path)
            is_valid, _ = validate_project_path(path)
            if is_valid:
                valid.append({
                    "name": p.name,
                    "path": p.path,
                    "created_at": p.created_at.isoformat() if p.created_at else None
                })
        return valid
    finally:
        session.close()


Good, I can see the end of the file. Now let me add the reference folder CRUD functions.


def list_valid_projects() -> list[dict[str, Any]]:
    """
    List all projects that have valid, accessible paths.

    Returns:
        List of project info dicts with additional 'name' field.
    """
    _, SessionLocal = _get_engine()
    session = SessionLocal()
    try:
        projects = session.query(Project).all()
        valid = []
        for p in projects:
            path = Path(p.path)
            is_valid, _ = validate_project_path(path)
            if is_valid:
                valid.append({
                    "name": p.name,
                    "path": p.path,
                    "created_at": p.created_at.isoformat() if p.created_at else None
                })
        return valid
    finally:
        session.close()

def list_valid_projects() -> list[dict[str, Any]]:
    """
    List all projects that have valid, accessible paths.

    Returns:
        List of project info dicts with additional 'name' field.
    """
    _, SessionLocal = _get_engine()
    session = SessionLocal()
    try:
        projects = session.query(Project).all()
        valid = []
        for p in projects:
            path = Path(p.path)
            is_valid, _ = validate_project_path(path)
            if is_valid:
                valid.append({
                    "name": p.name,
                    "path": p.path,
                    "created_at": p.created_at.isoformat() if p.created_at else None
                })
        return valid
    finally:
        session.close()

# =============================================================================
# Reference Folder Functions
# =============================================================================

def add_reference_folder(
    project_name: str,
    path: Path,
    alias: str | None = None,
    description: str | None = None,
) -> int:
    """
    Add a reference folder to a project.

    Args:
        project_name: The project to add the reference to.
        path: The absolute path to the reference folder.
        alias: Optional friendly name for the reference.
        description: Optional description of what this reference is for.

    Returns:
        The ID of the created reference folder.

    Raises:
        RegistryError: If the project doesn't exist.
        ValueError: If the path is not valid.
    """
    path = Path(path).resolve()

    # Validate path exists and is a directory
    if not path.exists():
        raise ValueError(f"Reference path does not exist: {path}")
    if not path.is_dir():
        raise ValueError(f"Reference path is not a directory: {path}")

    with _get_session() as session:
        # Check project exists
        project = session.query(Project).filter(Project.name == project_name).first()
        if not project:
            raise RegistryError(f"Project '{project_name}' not found")

        # Check for duplicate path
        existing = session.query(ReferenceFolder).filter(
            ReferenceFolder.project_name == project_name,
            ReferenceFolder.path == path.as_posix()
        ).first()
        if existing:
            raise RegistryError(f"Reference folder already exists for this project")

        ref = ReferenceFolder(
            project_name=project_name,
            path=path.as_posix(),
            alias=alias,
            description=description,
            created_at=datetime.now(),
        )
        session.add(ref)
        session.flush()  # Get the ID
        ref_id = ref.id

    logger.info("Added reference folder to '%s': %s", project_name, path)
    return ref_id

def remove_reference_folder(reference_id: int) -> bool:
    """
    Remove a reference folder by ID.

    Args:
        reference_id: The ID of the reference folder to remove.

    Returns:
        True if removed, False if not found.
    """
    with _get_session() as session:
        ref = session.query(ReferenceFolder).filter(ReferenceFolder.id == reference_id).first()
        if not ref:
            return False
        session.delete(ref)

    logger.info("Removed reference folder ID %d", reference_id)
    return True

def list_reference_folders(project_name: str) -> list[dict[str, Any]]:
    """
    List all reference folders for a project.

    Args:
        project_name: The project name.

    Returns:
        List of reference folder info dicts.
    """
    _, SessionLocal = _get_engine()
    session = SessionLocal()
    try:
        refs = session.query(ReferenceFolder).filter(
            ReferenceFolder.project_name == project_name
        ).all()
        return [
            {
                "id": r.id,
                "path": r.path,
                "alias": r.alias,
                "description": r.description,
                "created_at": r.created_at.isoformat() if r.created_at else None,
                "exists": Path(r.path).exists(),
            }
            for r in refs
        ]
    finally:
        session.close()

def get_reference_folder(reference_id: int) -> dict[str, Any] | None:
    """
    Get a reference folder by ID.

    Args:
        reference_id: The reference folder ID.

    Returns:
        Reference folder info dict, or None if not found.
    """
    _, SessionLocal = _get_engine()
    session = SessionLocal()
    try:
        ref = session.query(ReferenceFolder).filter(ReferenceFolder.id == reference_id).first()
        if not ref:
            return None
        return {
            "id": ref.id,
            "project_name": ref.project_name,
            "path": ref.path,
            "alias": ref.alias,
            "description": ref.description,
            "created_at": ref.created_at.isoformat() if ref.created_at else None,
            "exists": Path(ref.path).exists(),
        }
    finally:
        session.close()

def update_reference_folder(
    reference_id: int,
    alias: str | None = None,
    description: str | None = None,
) -> bool:
    """
    Update a reference folder's metadata.

    Args:
        reference_id: The reference folder ID.
        alias: New alias (pass None to keep unchanged).
        description: New description (pass None to keep unchanged).

    Returns:
        True if updated, False if not found.
    """
    with _get_session() as session:
        ref = session.query(ReferenceFolder).filter(ReferenceFolder.id == reference_id).first()
        if not ref:
            return False

        if alias is not None:
            ref.alias = alias
        if description is not None:
            ref.description = description

    return True