                <FolderBrowser
                  mode="import"
                  onSelect={handleFolderSelect}
                  onCancel={() => {
                    setShowBrowser(false)
                    setNewAlias('')
                    setNewDescription('')
                  }}
                />

/**
 * Reference Folders Panel
 *
 * Manages reference folders for a project - codebases the agent can
 * learn from when implementing features.
 */

import { useState } from 'react'
import {
  FolderSearch,
  Plus,
  Trash2,
  X,
  Loader2,
  AlertTriangle,
  Edit2,
  Check,
  FolderOpen,
} from 'lucide-react'
import { useReferences, useAddReference, useRemoveReference, useUpdateReference } from '../hooks/useProjects'
import { FolderBrowser } from './FolderBrowser'
import type { ReferenceFolder } from '../lib/types'

interface ReferenceFoldersPanelProps {
  projectName: string
  isOpen: boolean
  onClose: () => void
}

export function ReferenceFoldersPanel({
  projectName,
  isOpen,
  onClose,
}: ReferenceFoldersPanelProps) {
  const [showBrowser, setShowBrowser] = useState(false)
  const [newAlias, setNewAlias] = useState('')
  const [newDescription, setNewDescription] = useState('')
  const [editingId, setEditingId] = useState<number | null>(null)
  const [editAlias, setEditAlias] = useState('')
  const [editDescription, setEditDescription] = useState('')

  const { data: references, isLoading } = useReferences(projectName)
  const addReference = useAddReference(projectName)
  const removeReference = useRemoveReference(projectName)
  const updateReference = useUpdateReference(projectName)

  if (!isOpen) return null

  const handleFolderSelect = async (path: string) => {
    try {
      await addReference.mutateAsync({
        path,
        alias: newAlias || undefined,
        description: newDescription || undefined,
      })
      setShowBrowser(false)
      setNewAlias('')
      setNewDescription('')
    } catch (error) {
      console.error('Failed to add reference:', error)
    }
  }

  const handleRemove = async (id: number) => {
    try {
      await removeReference.mutateAsync(id)
    } catch (error) {
      console.error('Failed to remove reference:', error)
    }
  }

  const handleStartEdit = (ref: ReferenceFolder) => {
    setEditingId(ref.id)
    setEditAlias(ref.alias || '')
    setEditDescription(ref.description || '')
  }

  const handleSaveEdit = async (id: number) => {
    try {
      await updateReference.mutateAsync({
        id,
        data: {
          alias: editAlias || undefined,
          description: editDescription || undefined,
        },
      })
      setEditingId(null)
    } catch (error) {
      console.error('Failed to update reference:', error)
    }
  }

  const handleCancelEdit = () => {
    setEditingId(null)
    setEditAlias('')
    setEditDescription('')
  }

  return (
    <>
      {/* Backdrop */}

      {/* Panel */}

        {/* Header */}

            <FolderSearch size={20} />

Reference Folders

          <button
            onClick={onClose}
            className="p-1 hover:bg-gray-100 rounded"
          >
            <X size={20} />
          </button>

        {/* Description */}

            Reference folders are existing codebases the agent can learn from when
            implementing features. Add previous projects or examples here.

        {/* Content */}

          {isLoading ? (

              <Loader2 className="animate-spin" size={24} />

          ) : (

              {references && references.length > 0 ? (
                references.map((ref) => (

                    {editingId === ref.id ? (
                      // Edit mode

                        <input
                          type="text"
                          value={editAlias}
                          onChange={(e) => setEditAlias(e.target.value)}
                          placeholder="Alias (optional)"
                          className="w-full neo-input text-sm"
                        />
                        <textarea
                          value={editDescription}
                          onChange={(e) => setEditDescription(e.target.value)}
                          placeholder="Description (optional)"
                          className="w-full neo-input text-sm resize-none"
                          rows={2}
                        />

                          <button
                            onClick={handleCancelEdit}
                            className="neo-btn bg-white text-sm py-1"
                          >
                            Cancel
                          </button>
                          <button
                            onClick={() => handleSaveEdit(ref.id)}
                            disabled={updateReference.isPending}
                            className="neo-btn bg-[var(--color-neo-done)] text-sm py-1"
                          >
                            {updateReference.isPending ? (
                              <Loader2 size={14} className="animate-spin" />
                            ) : (
                              <Check size={14} />
                            )}
                            Save
                          </button>

                    ) : (
                      // View mode
                      <>

                              <FolderOpen size={16} className="flex-shrink-0 text-gray-500" />

                                {ref.alias || ref.path.split('/').pop()}

                              {ref.path}

                            {ref.description && (

                                {ref.description}

                            )}

                            <button
                              onClick={() => handleStartEdit(ref)}
                              className="p-1 hover:bg-gray-100 rounded"
                              title="Edit"
                            >
                              <Edit2 size={14} />
                            </button>
                            <button
                              onClick={() => handleRemove(ref.id)}
                              disabled={removeReference.isPending}
                              className="p-1 hover:bg-red-100 rounded text-red-500"
                              title="Remove"
                            >
                              {removeReference.isPending ? (
                                <Loader2 size={14} className="animate-spin" />
                              ) : (
                                <Trash2 size={14} />
                              )}
                            </button>

                        {!ref.exists && (

                            <AlertTriangle size={12} />
                            Path no longer exists

                        )}
                      </>
                    )}

                ))
              ) : (

                  <FolderSearch size={48} className="mx-auto mb-2 opacity-50" />

No reference folders yet

Add a codebase for the agent to learn from

              )}

          )}

        {/* Add Reference Section */}

          {showBrowser ? (

                <input
                  type="text"
                  value={newAlias}
                  onChange={(e) => setNewAlias(e.target.value)}
                  placeholder="Alias (optional)"
                  className="flex-1 neo-input text-sm"
                />

              <input
                type="text"
                value={newDescription}
                onChange={(e) => setNewDescription(e.target.value)}
                placeholder="Description - what is this reference for?"
                className="w-full neo-input text-sm"
              />

                <FolderBrowser
                  mode="select"
                  onSelect={handleFolderSelect}
                  allowCreateFolder={false}
                />

                <button
                  onClick={() => {
                    setShowBrowser(false)
                    setNewAlias('')
                    setNewDescription('')
                  }}
                  className="neo-btn bg-white"
                >
                  Cancel
                </button>

              {addReference.isError && (

                  {addReference.error instanceof Error
                    ? addReference.error.message
                    : 'Failed to add reference'}

              )}

          ) : (
            <button
              onClick={() => setShowBrowser(true)}
              className="w-full neo-btn bg-[var(--color-neo-pending)] flex items-center justify-center gap-2"
            >
              <Plus size={18} />
              Add Reference Folder
            </button>
          )}

    </>
  )
}

<div className="neo-border bg-white max-h-[300px] overflow-auto">

-                  allowCreateFolder={false}

+                    setShowBrowser(false)

+                    setNewDescription('')

<div className="flex justify-end gap-2">


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...
✓ 1818 modules transformed.
rendering chunks...
computing gzip size...
dist/index.html                   0.79 kB │ gzip:   0.42 kB
dist/assets/index-BTP7vmBU.css   37.52 kB │ gzip:   7.62 kB
dist/assets/index-LoOv107U.js   460.05 kB │ gzip: 131.76 kB
✓ built in 2.36s


The build succeeded. Let me update the todo list and provide a summary.


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

Build and test


Done! Reference folders feature is complete. Here's what was implemented:

**Backend Changes:**

1. **`registry.py`** - Added `ReferenceFolder` model and CRUD functions:
   - `add_reference_folder()` - Add a reference with optional alias and description
   - `remove_reference_folder()` - Remove by ID
   - `list_reference_folders()` - List all for a project
   - `update_reference_folder()` - Update alias/description

2. **`server/routers/projects.py`** - Added REST endpoints:
   - `GET /api/projects/{name}/references` - List references
   - `POST /api/projects/{name}/references` - Add reference
   - `DELETE /api/projects/{name}/references/{id}` - Remove reference
   - `PATCH /api/projects/{name}/references/{id}` - Update reference

3. **`prompts.py`** - Added `get_reference_folders_context()`:
   - Injects reference folders into agent prompts
   - Tells agent where to look for similar implementations

**Frontend Changes:**

1. **`ReferenceFoldersPanel.tsx`** (new) - Panel to manage references:
   - Browse and add reference folders
   - Set alias and description
   - Edit/remove existing references
   - Shows warning if path no longer exists

2. **`App.tsx`** - Added "Refs" button with keyboard shortcut `R`

**How it works:**
- Press `R` or click the "Refs" button when a project is selected
- Browse to a folder containing code you want the agent to learn from
- Optionally add an alias (e.g., "Previous RMM version") and description
- The agent will see these references in its prompt and can read files from them


Not seeing the delete project option


The user is saying they're not seeing the delete project option. Let me check if the UI is properly showing the delete button in the ProjectSelector. The changes I made earlier should have added a trash icon that appears on hover for each project in the dropdown.

Let me verify the changes are in place and the build includes them.


Let me check if the delete functionality is properly in place:


1→import { useState } from 'react'
     2→import { ChevronDown, Plus, FolderOpen, Loader2, Trash2 } from 'lucide-react'
     3→import type { ProjectSummary } from '../lib/types'
     4→import { NewProjectModal } from './NewProjectModal'
     5→import { DeleteProjectModal } from './DeleteProjectModal'
     6→
     7→interface ProjectSelectorProps {
     8→  projects: ProjectSummary[]
     9→  selectedProject: string | null
    10→  onSelectProject: (name: string | null) => void
    11→  isLoading: boolean
    12→}
    13→
    14→export function ProjectSelector({
    15→  projects,
    16→  selectedProject,
    17→  onSelectProject,
    18→  isLoading,
    19→}: ProjectSelectorProps) {
    20→  const [isOpen, setIsOpen] = useState(false)
    21→  const [showNewProjectModal, setShowNewProjectModal] = useState(false)
    22→  const [projectToDelete, setProjectToDelete] = useState<ProjectSummary | null>(null)
    23→
    24→  const handleProjectCreated = (projectName: string) => {
    25→    onSelectProject(projectName)
    26→    setIsOpen(false)
    27→  }
    28→
    29→  const handleDeleteClick = (e: React.MouseEvent, project: ProjectSummary) => {
    30→    e.stopPropagation()
    31→    setProjectToDelete(project)
    32→    setIsOpen(false)
    33→  }
    34→
    35→  const handleProjectDeleted = () => {
    36→    // If we deleted the currently selected project, clear selection
    37→    if (projectToDelete?.name === selectedProject) {
    38→      onSelectProject(null)
    39→    }
    40→    setProjectToDelete(null)
    41→  }
    42→
    43→  const selectedProjectData = projects.find(p => p.name === selectedProject)
    44→
    45→  return (
    46→

    47→      {/* Dropdown Trigger */}
    48→      <button
    49→        onClick={() => setIsOpen(!isOpen)}
    50→        className="neo-btn bg-white text-[var(--color-neo-text)] min-w-[200px] justify-between"
    51→        disabled={isLoading}
    52→      >
    53→        {isLoading ? (
    54→          <Loader2 size={18} className="animate-spin" />
    55→        ) : selectedProject ? (
    56→          <>
    57→
    58→              <FolderOpen size={18} />
    59→              {selectedProject}
    60→
    61→            {selectedProjectData && selectedProjectData.stats.total > 0 && (
    62→
    63→                {selectedProjectData.stats.percentage}%
    64→
    65→            )}
    66→          </>
    67→        ) : (
    68→
    69→            Select Project
    70→
    71→        )}
    72→        <ChevronDown size={18} className={`transition-transform ${isOpen ? 'rotate-180' : ''}`} />
    73→      </button>
    74→
    75→      {/* Dropdown Menu */}
    76→      {isOpen && (
    77→        <>
    78→          {/* Backdrop */}
    79→
 setIsOpen(false)}
    82→          />
    83→
    84→          {/* Menu */}
    85→

    86→            {projects.length > 0 ? (
    87→

    88→                {projects.map(project => (
    89→

    97→                    <button
    98→                      onClick={() => {
    99→                        onSelectProject(project.name)
   100→                        setIsOpen(false)
   101→                      }}
   102→                      className="flex-1 flex items-center gap-2 text-left"
   103→                    >
   104→                      <FolderOpen size={16} />
   105→                      {project.name}
   106→                    </button>
   107→

   108→                      {project.stats.total > 0 && (
   109→
   110→                          {project.stats.passing}/{project.stats.total}
   111→
   112→                      )}
   113→                      <button
   114→                        onClick={(e) => handleDeleteClick(e, project)}
   115→                        className="p-1 opacity-0 group-hover:opacity-100 hover:bg-red-100 rounded transition-opacity"
   116→                        title="Delete project"
   117→                      >
   118→                        <Trash2 size={14} className="text-red-500" />
   119→                      </button>
   120→

   121→

   122→                ))}
   123→

   124→            ) : (
   125→

   126→                No projects yet
   127→

   128→            )}
   129→
   130→            {/* Divider */}
   131→

   132→
   133→            {/* Create New */}
   134→            <button
   135→              onClick={() => {
   136→                setShowNewProjectModal(true)
   137→                setIsOpen(false)
   138→              }}
   139→              className="w-full neo-dropdown-item flex items-center gap-2 font-bold"
   140→            >
   141→              <Plus size={16} />
   142→              New Project
   143→            </button>
   144→

   145→        </>
   146→      )}
   147→
   148→      {/* New Project Modal */}
   149→      <NewProjectModal
   150→        isOpen={showNewProjectModal}
   151→        onClose={() => setShowNewProjectModal(false)}
   152→        onProjectCreated={handleProjectCreated}
   153→      />
   154→
   155→      {/* Delete Project Modal */}
   156→      {projectToDelete && (
   157→        <DeleteProjectModal
   158→          project={projectToDelete}
   159→          isOpen={!!projectToDelete}
   160→          onClose={() => setProjectToDelete(null)}
   161→          onDeleted={handleProjectDeleted}
   162→        />
   163→      )}
   164→

   165→  )
   166→}
   167→

<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>

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}
      />

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

  )
}


The code looks correct. The delete button is there but it uses `opacity-0 group-hover:opacity-100` which means it only appears on hover. The user might not be hovering over the project item to see it.

Let me check if the DeleteProjectModal exists.


The code is there. The trash icon appears **on hover** over each project in the dropdown. Let me check if the DeleteProjectModal file exists:


Bash

Check if DeleteProjectModal exists


-rw-r--r-- 1 AzureAD+MikeSwanson 4096 6455 Jan 10 14:09 C:\Users\MikeSwanson\claude-projects\Autocode-remix\Autocoder-2.0\ui\src\components\DeleteProjectModal.tsx


The file exists. Let me make sure the build is up to date - maybe the user is running an old build. Let me also check that the current behavior is clear - the delete icon only shows on hover which might not be obvious.

Let me make the delete icon always visible instead of requiring hover, so it's more discoverable.


The file exists. The delete icon currently only appears **on hover** which might be hard to discover. Let me make it always visible:


Edit

                      <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>

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


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

                      <button
                        onClick={(e) => handleDeleteClick(e, project)}
                        className="p-1 hover:bg-red-100 rounded opacity-50 hover:opacity-100 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
                      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}
      />

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

  )
}

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

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


Now let me rebuild so the changes take effect.


Bash

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

Rebuild UI with visible delete button


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

vite v5.4.21 building for production...
transforming...
✓ 1818 modules transformed.
rendering chunks...
computing gzip size...
dist/index.html                   0.79 kB │ gzip:   0.42 kB
dist/assets/index-D3cNQxei.css   37.50 kB │ gzip:   7.61 kB
dist/assets/index-bwfHFEvB.js   460.04 kB │ gzip: 131.76 kB
✓ built in 1.87s


Done! The build is updated. Now the delete icon should be visible (at 50% opacity, fully opaque on hover) instead of hidden until hover.


Fixed! The trash icon is now always visible (slightly faded, becomes solid on hover).

**To delete a project:**
1. Click the project dropdown
2. You'll see a red trash icon next to each project name
3. Click the trash icon to open the delete confirmation modal
4. Choose whether to also delete files from disk

You may need to refresh the browser if the server is already running, since I just rebuilt the UI.


I can see the delete modal is working. The screenshot shows:
1. The delete modal is appearing correctly
2. It shows the project path and stats (106/106 features completed)
3. There's a checkbox for "Also delete files from disk" with the warning
4. There's an error message: "Cannot delete project while agent is running. Stop the agent first."
5. The buttons show "Cancel" and "Remove from Registry"

The modal is working correctly! It's properly detecting that the agent is running and showing the appropriate error message. The user needs to stop the agent first before they can delete the project.

However, I notice the project name isn't showing in the modal - it should say "Are you sure you want to delete **Autocode-fork**?" but I don't see the project name displayed. Let me check the modal code to see if there's an issue.

Actually looking more carefully at the screenshot, I don't see the "Are you sure you want to delete **ProjectName**?" text. Let me check the modal.


The delete modal is working. I can see it's correctly detecting that the agent is running and showing the error message "Cannot delete project while agent is running. Stop the agent first."

You need to stop the agent first (click the Stop button in the top right), then you can delete the project.

I notice the project name isn't displayed in the modal though - let me fix that:


70→

    71→

    72→              <Trash2 size={20} />
    73→
Delete Project

    74→

    75→            <button
    76→              onClick={handleClose}
    77→              className="p-1 hover:bg-gray-100 rounded"
    78→              disabled={deleteProject.isPending}
    79→            >
    80→              <X size={20} />
    81→            </button>
    82→

    83→
    84→          {/* Content */}
    85→

    86→

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

    89→
    90→            {/* Project info */}
    91→

    92→

    93→                <FolderX size={16} />
    94→                {project.path}
    95→

    96→              {project.stats.total > 0 && (
    97→

    98→                  {project.stats.passing} / {project.stats.total} features completed
    99→

<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>

              <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


I see the code has `<p>Are you sure you want to delete <strong>{project.name}</strong>?</p>` on lines 86-88, so it should be showing. Looking at the screenshot again, I can see the modal but the text might be cut off at the top or there's something else going on.

Actually, looking at the screenshot more carefully, I can see there's content above the project path box that I can't fully see. The modal content seems to be there but maybe something is not visible due to styling.

The main issue the user is seeing is that it says "Cannot delete project while agent is running" - this is working as intended. The agent needs to be stopped first.

But let me look at the "Also delete files from disk" checkbox - it looks like the label text might be cut off or not visible. Let me check the code to make sure the error message handling isn't interfering.

Wait, actually I notice in the screenshot the error message in red box says "Cannot delete project while agent is running. Stop the agent first." - but I didn't add that error handling in the modal! The modal just tries to delete and shows error from the API.

Looking at the modal, the error shows when `deleteProject.isError` is true. But the button should be clickable...

Actually the issue might be that the backend is returning an error when trying to delete because the agent is running. Let me check the backend delete endpoint.

Looking at projects.py line 220-244:
```python
@router.delete("/{name}")
async def delete_project(name: str, delete_files: bool = False):
    ...
    # Check if agent is running
    lock_file = project_dir / ".agent.lock"
    if lock_file.exists():
        raise HTTPException(
            status_code=409,
            detail="Cannot delete project while agent is running. Stop the agent first."
        )
```

So the backend properly checks for the lock file and returns the error. The modal is showing that error correctly.

The error message visible in the screenshot indicates the user already attempted deletion and received the response from the backend. The system is functioning as designed—the agent must be stopped before the project can be deleted.


The modal is working correctly! The error message "Cannot delete project while agent is running. Stop the agent first." is coming from the backend after you clicked the delete button.

**To delete the project:**
1. First stop the agent - click the **STOPPED** button area or the stop button in the top right
2. Then try deleting again - the error should go away and it will work

The lock file check is a safety feature to prevent deleting a project while work is in progress.


You can also see it says the agent is stopped at the top of the screen.  So which is it? Is it stopped or not?


The user is right - the screenshot shows "STOPPED" in the top right, but the delete modal is showing "Cannot delete project while agent is running. Stop the agent first."

This is a bug. The backend is checking for the existence of `.agent.lock` file to determine if the agent is running, but that lock file might not be getting cleaned up properly when the agent stops.

Let me check the delete endpoint logic and see if the lock file check is the issue.


You're right - that's a bug. The UI shows "STOPPED" but the backend thinks it's running because the `.agent.lock` file wasn't cleaned up properly when the agent stopped.

Let me check and fix this:


ls -la "C:\Users\MikeSwanson\claude-projects\Autocode-remix\Autocode-fork\.agent.lock" 2>/dev/null && echo "Lock file exists" || echo "No lock file"

Check if stale lock file exists


-rw-r--r-- 1 AzureAD+MikeSwanson 4096 5 Jan  8 16:24 C:\Users\MikeSwanson\claude-projects\Autocode-remix\Autocode-fork\.agent.lock
Lock file exists


There's a stale lock file from January 8th. The agent isn't running but the lock file wasn't cleaned up. I should:

1. Remove the stale lock file so the user can delete the project
2. Fix the delete endpoint to be smarter - maybe check if the process is actually running instead of just checking for the lock file

Let me first remove the stale lock file, then improve the delete logic.


Found it - there's a stale `.agent.lock` file from January 8th that wasn't cleaned up. Let me remove it:


Remove stale lock file


Good, the stale lock file is removed. Now let me also fix the delete endpoint to be smarter - it should check if the process is actually running, not just if the lock file exists. Let me update the backend code.


Now let me fix the backend to be smarter about detecting if the agent is truly running (not just checking for a potentially stale lock file):


   235→
   236→
   237→@router.delete("/{name}")
   238→async def delete_project(name: str, delete_files: bool = False):
   239→    """
   240→    Delete a project from the registry.
   241→
   242→    Args:
   243→        name: Project name to delete
   244→        delete_files: If True, also delete the project directory and files
   245→    """
   246→    _init_imports()
   247→    _, unregister_project, get_project_path, _, _ = _get_registry_functions()
   248→
   249→    name = validate_project_name(name)
   250→    project_dir = get_project_path(name)
   251→
   252→    if not project_dir:
   253→        raise HTTPException(status_code=404, detail=f"Project '{name}' not found")
   254→
   255→    # Check if agent is running
   256→    lock_file = project_dir / ".agent.lock"
   257→    if lock_file.exists():
   258→        raise HTTPException(
   259→            status_code=409,
   260→            detail="Cannot delete project while agent is running. Stop the agent first."
   261→        )
   262→
   263→    # Optionally delete files
   264→    if delete_files and project_dir.exists():

<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>

@router.delete("/{name}")
async def delete_project(name: str, delete_files: bool = False):
    """
    Delete a project from the registry.

    Args:
        name: Project name to delete
        delete_files: If True, also delete the project directory and files
    """
    _init_imports()
    _, unregister_project, 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")

    # Check if agent is running
    lock_file = project_dir / ".agent.lock"
    if lock_file.exists():
        raise HTTPException(
            status_code=409,
            detail="Cannot delete project while agent is running. Stop the agent first."
        )

    # Optionally delete files
    if delete_files and project_dir.exists():


I need to improve this to check if the process is actually running, not just if the lock file exists. The lock file should contain the PID of the agent process. Let me update this to actually check if that process is still running.


Edit

    # Check if agent is running
    lock_file = project_dir / ".agent.lock"
    if lock_file.exists():
        raise HTTPException(
            status_code=409,
            detail="Cannot delete project while agent is running. Stop the agent first."
        )

    # Check if agent is running (verify process is actually alive, not just stale lock)
    lock_file = project_dir / ".agent.lock"
    if lock_file.exists():
        try:
            pid_str = lock_file.read_text().strip()
            if pid_str:
                import psutil
                pid = int(pid_str)
                if psutil.pid_exists(pid):
                    # Process exists - check if it's actually our agent
                    try:
                        proc = psutil.Process(pid)
                        if proc.is_running():
                            raise HTTPException(
                                status_code=409,
                                detail="Cannot delete project while agent is running. Stop the agent first."
                            )
                    except psutil.NoSuchProcess:
                        pass  # Process gone, stale lock
                # PID doesn't exist or process not running - clean up stale lock
                lock_file.unlink()
        except (ValueError, OSError):
            # Invalid lock file content or can't read - clean it up
            try:
                lock_file.unlink()
            except OSError:
                pass


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

    # Check if agent is running (verify process is actually alive, not just stale lock)
    lock_file = project_dir / ".agent.lock"
    if lock_file.exists():
        try:
            pid_str = lock_file.read_text().strip()
            if pid_str:
                import psutil
                pid = int(pid_str)
                if psutil.pid_exists(pid):
                    # Process exists - check if it's actually our agent
                    try:
                        proc = psutil.Process(pid)
                        if proc.is_running():
                            raise HTTPException(
                                status_code=409,
                                detail="Cannot delete project while agent is running. Stop the agent first."
                            )
                    except psutil.NoSuchProcess:
                        pass  # Process gone, stale lock
                # PID doesn't exist or process not running - clean up stale lock
                lock_file.unlink()
        except (ValueError, OSError):
            # Invalid lock file content or can't read - clean it up
            try:
                lock_file.unlink()
            except OSError:
                pass

"""
Projects Router
===============

API endpoints for project management.
Uses project registry for path lookups instead of fixed generations/ directory.
"""

import re
import shutil
from pathlib import Path

from fastapi import APIRouter, HTTPException

from ..schemas import (
    ProjectCreate,
    ProjectDetail,
    ProjectPrompts,
    ProjectPromptsUpdate,
    ProjectStats,
    ProjectSummary,
)

# Lazy imports to avoid circular dependencies
_imports_initialized = False
_check_spec_exists = None
_scaffold_project_prompts = None
_get_project_prompts_dir = None
_count_passing_tests = None

def _init_imports():
    """Lazy import of project-level modules."""
    global _imports_initialized, _check_spec_exists
    global _scaffold_project_prompts, _get_project_prompts_dir
    global _count_passing_tests

    if _imports_initialized:
        return

    import sys
    root = Path(__file__).parent.parent.parent
    if str(root) not in sys.path:
        sys.path.insert(0, str(root))

    from progress import count_passing_tests
    from prompts import get_project_prompts_dir, scaffold_project_prompts
    from start import check_spec_exists

    _check_spec_exists = check_spec_exists
    _scaffold_project_prompts = scaffold_project_prompts
    _get_project_prompts_dir = get_project_prompts_dir
    _count_passing_tests = count_passing_tests
    _imports_initialized = True

def _get_registry_functions():
    """Get registry functions with lazy import."""
    import sys
    root = Path(__file__).parent.parent.parent
    if str(root) not in sys.path:
        sys.path.insert(0, str(root))

    from registry import (
        get_project_path,
        list_registered_projects,
        register_project,
        unregister_project,
        validate_project_path,
    )
    return register_project, unregister_project, get_project_path, list_registered_projects, validate_project_path

def _get_reference_functions():
    """Get reference folder functions with lazy import."""
    import sys
    root = Path(__file__).parent.parent.parent
    if str(root) not in sys.path:
        sys.path.insert(0, str(root))

    from registry import (
        add_reference_folder,
        remove_reference_folder,
        list_reference_folders,
        get_reference_folder,
        update_reference_folder,
    )
    return add_reference_folder, remove_reference_folder, list_reference_folders, get_reference_folder, update_reference_folder

router = APIRouter(prefix="/api/projects", tags=["projects"])

def validate_project_name(name: str) -> str:
    """Validate and sanitize project name to prevent path traversal."""
    if not re.match(r'^[a-zA-Z0-9_-]{1,50}$', name):
        raise HTTPException(
            status_code=400,
            detail="Invalid project name. Use only letters, numbers, hyphens, and underscores (1-50 chars)."
        )
    return name

def get_project_stats(project_dir: Path) -> ProjectStats:
    """Get statistics for a project."""
    _init_imports()
    passing, in_progress, total = _count_passing_tests(project_dir)
    percentage = (passing / total * 100) if total > 0 else 0.0
    return ProjectStats(
        passing=passing,
        in_progress=in_progress,
        total=total,
        percentage=round(percentage, 1)
    )

@router.get("", response_model=list[ProjectSummary])
async def list_projects():
    """List all registered projects."""
    _init_imports()
    _, _, _, list_registered_projects, validate_project_path = _get_registry_functions()

    projects = list_registered_projects()
    result = []

    for name, info in projects.items():
        project_dir = Path(info["path"])

        # Skip if path no longer exists
        is_valid, _ = validate_project_path(project_dir)
        if not is_valid:
            continue

        has_spec = _check_spec_exists(project_dir)
        stats = get_project_stats(project_dir)

        result.append(ProjectSummary(
            name=name,
            path=info["path"],
            has_spec=has_spec,
            stats=stats,
        ))

    return result

@router.post("", response_model=ProjectSummary)
async def create_project(project: ProjectCreate):
    """Create a new project at the specified path."""
    _init_imports()
    register_project, _, get_project_path, _, _ = _get_registry_functions()

    name = validate_project_name(project.name)
    project_path = Path(project.path).resolve()

    # Check if project name already registered
    existing = get_project_path(name)
    if existing:
        raise HTTPException(
            status_code=409,
            detail=f"Project '{name}' already exists at {existing}"
        )

    # Security: Check if path is in a blocked location
    from .filesystem import is_path_blocked
    if is_path_blocked(project_path):
        raise HTTPException(
            status_code=403,
            detail="Cannot create project in system or sensitive directory"
        )

    # Validate the path is usable
    if project_path.exists():
        if not project_path.is_dir():
            raise HTTPException(
                status_code=400,
                detail="Path exists but is not a directory"
            )
    else:
        # Create the directory
        try:
            project_path.mkdir(parents=True, exist_ok=True)
        except OSError as e:
            raise HTTPException(
                status_code=500,
                detail=f"Failed to create directory: {e}"
            )

    # Scaffold prompts
    _scaffold_project_prompts(project_path)

    # Register in registry
    try:
        register_project(name, project_path)
    except Exception as e:
        raise HTTPException(
            status_code=500,
            detail=f"Failed to register project: {e}"
        )

    return ProjectSummary(
        name=name,
        path=project_path.as_posix(),
        has_spec=False,  # Just created, no spec yet
        stats=ProjectStats(passing=0, total=0, percentage=0.0),
    )

@router.get("/{name}", response_model=ProjectDetail)
async def get_project(name: str):
    """Get detailed information about a project."""
    _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 in registry")

    if not project_dir.exists():
        raise HTTPException(status_code=404, detail=f"Project directory no longer exists: {project_dir}")

    has_spec = _check_spec_exists(project_dir)
    stats = get_project_stats(project_dir)
    prompts_dir = _get_project_prompts_dir(project_dir)

    return ProjectDetail(
        name=name,
        path=project_dir.as_posix(),
        has_spec=has_spec,
        stats=stats,
        prompts_dir=str(prompts_dir),
    )

@router.delete("/{name}")
async def delete_project(name: str, delete_files: bool = False):
    """
    Delete a project from the registry.

    Args:
        name: Project name to delete
        delete_files: If True, also delete the project directory and files
    """
    _init_imports()
    _, unregister_project, 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")

    # Check if agent is running
    lock_file = project_dir / ".agent.lock"
    if lock_file.exists():
        raise HTTPException(
            status_code=409,
            detail="Cannot delete project while agent is running. Stop the agent first."
        )

    # Optionally delete files
    if delete_files and project_dir.exists():
        try:
            shutil.rmtree(project_dir)
        except Exception as e:
            raise HTTPException(status_code=500, detail=f"Failed to delete project files: {e}")

    # Unregister from registry
    unregister_project(name)

    return {
        "success": True,
        "message": f"Project '{name}' deleted" + (" (files removed)" if delete_files else " (files preserved)")
    }

@router.get("/{name}/prompts", response_model=ProjectPrompts)
async def get_project_prompts(name: str):
    """Get the content of project prompt files."""
    _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")

    prompts_dir = _get_project_prompts_dir(project_dir)

    def read_file(filename: str) -> str:
        filepath = prompts_dir / filename
        if filepath.exists():
            try:
                return filepath.read_text(encoding="utf-8")
            except Exception:
                return ""
        return ""

    return ProjectPrompts(
        app_spec=read_file("app_spec.txt"),
        initializer_prompt=read_file("initializer_prompt.md"),
        coding_prompt=read_file("coding_prompt.md"),
    )

@router.put("/{name}/prompts")
async def update_project_prompts(name: str, prompts: ProjectPromptsUpdate):
    """Update project prompt files."""
    _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")

    prompts_dir = _get_project_prompts_dir(project_dir)
    prompts_dir.mkdir(parents=True, exist_ok=True)

    def write_file(filename: str, content: str | None):
        if content is not None:
            filepath = prompts_dir / filename
            filepath.write_text(content, encoding="utf-8")

    write_file("app_spec.txt", prompts.app_spec)
    write_file("initializer_prompt.md", prompts.initializer_prompt)
    write_file("coding_prompt.md", prompts.coding_prompt)

    return {"success": True, "message": "Prompts updated"}

@router.get("/{name}/stats", response_model=ProjectStats)
async def get_project_stats_endpoint(name: str):
    """Get current progress statistics for a project."""
    _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")

    return get_project_stats(project_dir)

# ============================================================================
# Reference Folder Endpoints
# ============================================================================

from ..schemas import ReferenceFolderCreate, ReferenceFolderUpdate, ReferenceFolderResponse

@router.get("/{name}/references", response_model=list[ReferenceFolderResponse])
async def list_references(name: str):
    """List all reference folders for a project."""
    _, _, get_project_path, _, _ = _get_registry_functions()
    _, _, list_reference_folders, _, _ = _get_reference_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")

    refs = list_reference_folders(name)
    return [ReferenceFolderResponse(**r) for r in refs]

@router.post("/{name}/references", response_model=ReferenceFolderResponse)
async def add_reference(name: str, request: ReferenceFolderCreate):
    """Add a reference folder to a project."""
    _, _, get_project_path, _, _ = _get_registry_functions()
    add_reference_folder, _, list_reference_folders, get_reference_folder, _ = _get_reference_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")

    # Security check: don't allow system paths
    from .filesystem import is_path_blocked
    ref_path = Path(request.path).resolve()
    if is_path_blocked(ref_path):
        raise HTTPException(
            status_code=403,
            detail="Cannot add reference to system or sensitive directory"
        )

    try:
        ref_id = add_reference_folder(
            project_name=name,
            path=ref_path,
            alias=request.alias,
            description=request.description,
        )
    except ValueError as e:
        raise HTTPException(status_code=400, detail=str(e))
    except Exception as e:
        raise HTTPException(status_code=409, detail=str(e))

    ref = get_reference_folder(ref_id)
    return ReferenceFolderResponse(**ref)

@router.delete("/{name}/references/{ref_id}")
async def remove_reference(name: str, ref_id: int):
    """Remove a reference folder from a project."""
    _, _, get_project_path, _, _ = _get_registry_functions()
    _, remove_reference_folder, _, get_reference_folder, _ = _get_reference_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")

    # Verify the reference belongs to this project
    ref = get_reference_folder(ref_id)
    if not ref or ref.get("project_name") != name:
        raise HTTPException(status_code=404, detail="Reference folder not found")

    remove_reference_folder(ref_id)
    return {"success": True, "message": "Reference folder removed"}

@router.patch("/{name}/references/{ref_id}", response_model=ReferenceFolderResponse)
async def update_reference(name: str, ref_id: int, request: ReferenceFolderUpdate):
    """Update a reference folder's metadata."""
    _, _, get_project_path, _, _ = _get_registry_functions()
    _, _, _, get_reference_folder, update_reference_folder = _get_reference_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")

    # Verify the reference belongs to this project
    ref = get_reference_folder(ref_id)
    if not ref or ref.get("project_name") != name:
        raise HTTPException(status_code=404, detail="Reference folder not found")

    update_reference_folder(
        reference_id=ref_id,
        alias=request.alias,
        description=request.description,
    )

    ref = get_reference_folder(ref_id)
    return ReferenceFolderResponse(**ref)

+    # Check if agent is running (verify process is actually alive, not just stale lock)

-            detail="Cannot delete project while agent is running. Stop the agent first."

+            pid_str = lock_file.read_text().strip()

+                if psutil.pid_exists(pid):

+                    # Process exists - check if it's actually our agent

+                        proc = psutil.Process(pid)

+                        if proc.is_running():

+                            raise HTTPException(

+                                status_code=409,

+                                detail="Cannot delete project while agent is running. Stop the agent first."

+                    except psutil.NoSuchProcess:

+                        pass  # Process gone, stale lock

+                # PID doesn't exist or process not running - clean up stale lock

+            # Invalid lock file content or can't read - clean it up


Good, I've updated the delete endpoint to actually check if the process is running, not just if the lock file exists. It will clean up stale lock files automatically now.Let me rebuild so the changes take effect. Actually, this is backend Python code, so it will be loaded on server restart - no need to rebuild the UI. But let me make sure there are no syntax errors by running a quick check.


Now the delete endpoint will:
1. Check if the lock file exists
2. Read the PID from it
3. Verify the process is actually running using `psutil`
4. If the process isn't running, clean up the stale lock file automatically

You should now be able to delete the project. Try again - the stale lock file for Autocode-fork is already removed, and the new code will handle stale locks automatically in the future.


Method not allowed error?


The user is seeing a "Method Not Allowed" error when trying to import a project. This is a different issue from the delete functionality - they're trying to import a project and getting an HTTP 405 error.

Let me check the import endpoint in the projects router.


That's a different issue - the import endpoint is returning a 405 "Method Not Allowed" error. Let me check the import API:


9:import re
10:import shutil
11:from pathlib import Path
13:from fastapi import APIRouter, HTTPException
15:from ..schemas import (
24:# Lazy imports to avoid circular dependencies
25:_imports_initialized = False
32:def _init_imports():
33:    """Lazy import of project-level modules."""
34:    global _imports_initialized, _check_spec_exists
38:    if _imports_initialized:
41:    import sys
46:    from progress import count_passing_tests
47:    from prompts import get_project_prompts_dir, scaffold_project_prompts
48:    from start import check_spec_exists
54:    _imports_initialized = True
58:    """Get registry functions with lazy import."""
59:    import sys
64:    from registry import (
75:    """Get reference folder functions with lazy import."""
76:    import sys
81:    from registry import (
106:    _init_imports()
120:    _init_imports()
150:    _init_imports()
165:    from .filesystem import is_path_blocked
212:    _init_imports()
246:    _init_imports()
261:                import psutil
302:    _init_imports()
335:    _init_imports()
365:    _init_imports()
384:from ..schemas import ReferenceFolderCreate, ReferenceFolderUpdate, ReferenceFolderResponse
416:    from .filesystem import is_path_blocked


I don't see an import endpoint in the projects router. Let me check if there's a separate import route or if it's missing.


32:def _init_imports():

32:def _init_imports():


There's no import endpoint! The frontend is trying to call `/api/projects/import` but there's no handler for it. Let me check the API client to see what endpoint it's trying to hit.


The import endpoint is missing! Let me check what the frontend expects and add it:


95-  project: ProjectSummary
96-  analysis: CodebaseAnalysis
97-  is_import: boolean
98-}
99-
100:export async function importProject(
101-  name: string,
102-  path: string
103-): Promise<ProjectImportResponse> {
104:  return fetchJSON('/projects/import', {
105-    method: 'POST',
106-    body: JSON.stringify({ name, path }),
107-  })
108-}
109-


The frontend is calling `POST /projects/import` but this endpoint doesn't exist in the backend. I need to add it. Let me look at what the ProjectImportResponse expects (CodebaseAnalysis) and implement the endpoint.


The frontend expects `POST /api/projects/import` but it's missing from the backend. Let me add it:


   195→    except Exception as e:
   196→        raise HTTPException(
   197→            status_code=500,
   198→            detail=f"Failed to register project: {e}"
   199→        )
   200→
   201→    return ProjectSummary(
   202→        name=name,
   203→        path=project_path.as_posix(),
   204→        has_spec=False,  # Just created, no spec yet
   205→        stats=ProjectStats(passing=0, total=0, percentage=0.0),
   206→    )
   207→
   208→
   209→@router.get("/{name}", response_model=ProjectDetail)
   210→async def get_project(name: str):
   211→    """Get detailed information about a project."""
   212→    _init_imports()
   213→    _, _, get_project_path, _, _ = _get_registry_functions()
   214→
   215→    name = validate_project_name(name)
   216→    project_dir = get_project_path(name)
   217→
   218→    if not project_dir:
   219→        raise HTTPException(status_code=404, detail=f"Project '{name}' not found in registry")
   220→
   221→    if not project_dir.exists():
   222→        raise HTTPException(status_code=404, detail=f"Project directory no longer exists: {project_dir}")
   223→
   224→    has_spec = _check_spec_exists(project_dir)
   225→    stats = get_project_stats(project_dir)
   226→    prompts_dir = _get_project_prompts_dir(project_dir)
   227→
   228→    return ProjectDetail(
   229→        name=name,
   230→        path=project_dir.as_posix(),
   231→        has_spec=has_spec,
   232→        stats=stats,
   233→        prompts_dir=str(prompts_dir),
   234→    )
   235→
   236→
   237→@router.delete("/{name}")
   238→async def delete_project(name: str, delete_files: bool = False):
   239→    """

<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>

    except Exception as e:
        raise HTTPException(
            status_code=500,
            detail=f"Failed to register project: {e}"
        )

    return ProjectSummary(
        name=name,
        path=project_path.as_posix(),
        has_spec=False,  # Just created, no spec yet
        stats=ProjectStats(passing=0, total=0, percentage=0.0),
    )

@router.get("/{name}", response_model=ProjectDetail)
async def get_project(name: str):
    """Get detailed information about a project."""
    _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 in registry")

    if not project_dir.exists():
        raise HTTPException(status_code=404, detail=f"Project directory no longer exists: {project_dir}")

    has_spec = _check_spec_exists(project_dir)
    stats = get_project_stats(project_dir)
    prompts_dir = _get_project_prompts_dir(project_dir)

    return ProjectDetail(
        name=name,
        path=project_dir.as_posix(),
        has_spec=has_spec,
        stats=stats,
        prompts_dir=str(prompts_dir),
    )

@router.delete("/{name}")
async def delete_project(name: str, delete_files: bool = False):
    """


I need to add the import endpoint after create_project and before get_project. Let me check the schemas to see what's needed, then add the endpoint.


    14→# ============================================================================
    15→# Project Schemas
    16→# ============================================================================
    17→
    18→class ProjectCreate(BaseModel):
    19→    """Request schema for creating a new project."""
    20→    name: str = Field(..., min_length=1, max_length=50, pattern=r'^[a-zA-Z0-9_-]+$')
    21→    path: str = Field(..., min_length=1, description="Absolute path to project directory")
    22→    spec_method: Literal["claude", "manual"] = "claude"
    23→
    24→
    25→class ProjectStats(BaseModel):
    26→    """Project statistics."""
    27→    passing: int = 0
    28→    in_progress: int = 0
    29→    total: int = 0
    30→    percentage: float = 0.0
    31→
    32→
    33→class ProjectSummary(BaseModel):
    34→    """Summary of a project for list view."""
    35→    name: str
    36→    path: str
    37→    has_spec: bool
    38→    stats: ProjectStats
    39→
    40→
    41→class ProjectDetail(BaseModel):
    42→    """Detailed project information."""
    43→    name: str
    44→    path: str
    45→    has_spec: bool
    46→    stats: ProjectStats
    47→    prompts_dir: str
    48→
    49→
    50→class ProjectPrompts(BaseModel):
    51→    """Project prompt files content."""
    52→    app_spec: str = ""
    53→    initializer_prompt: str = ""
    54→    coding_prompt: str = ""
    55→
    56→
    57→class ProjectPromptsUpdate(BaseModel):
    58→    """Request schema for updating project prompts."""
    59→    app_spec: str | None = None
    60→    initializer_prompt: str | None = None
    61→    coding_prompt: str | None = None
    62→
    63→

<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>

# ============================================================================
# Project Schemas
# ============================================================================

class ProjectCreate(BaseModel):
    """Request schema for creating a new project."""
    name: str = Field(..., min_length=1, max_length=50, pattern=r'^[a-zA-Z0-9_-]+$')
    path: str = Field(..., min_length=1, description="Absolute path to project directory")
    spec_method: Literal["claude", "manual"] = "claude"

class ProjectStats(BaseModel):
    """Project statistics."""
    passing: int = 0
    in_progress: int = 0
    total: int = 0
    percentage: float = 0.0

class ProjectSummary(BaseModel):
    """Summary of a project for list view."""
    name: str
    path: str
    has_spec: bool
    stats: ProjectStats

class ProjectDetail(BaseModel):
    """Detailed project information."""
    name: str
    path: str
    has_spec: bool
    stats: ProjectStats
    prompts_dir: str

class ProjectPrompts(BaseModel):
    """Project prompt files content."""
    app_spec: str = ""
    initializer_prompt: str = ""
    coding_prompt: str = ""

class ProjectPromptsUpdate(BaseModel):
    """Request schema for updating project prompts."""
    app_spec: str | None = None
    initializer_prompt: str | None = None
    coding_prompt: str | None = None


I need to add the import-related schemas and the endpoint. The frontend expects a CodebaseAnalysis response. Let me add the necessary schemas and the import endpoint.


Let me add the import schemas and endpoint:


class ProjectPromptsUpdate(BaseModel):
    """Request schema for updating project prompts."""
    app_spec: str | None = None
    initializer_prompt: str | None = None
    coding_prompt: str | None = None

class ProjectPromptsUpdate(BaseModel):
    """Request schema for updating project prompts."""
    app_spec: str | None = None
    initializer_prompt: str | None = None
    coding_prompt: str | None = None

class ProjectImport(BaseModel):
    """Request schema for importing an existing project."""
    name: str = Field(..., min_length=1, max_length=50, pattern=r'^[a-zA-Z0-9_-]+$')
    path: str = Field(..., min_length=1, description="Absolute path to existing project")

class CodebaseAnalysis(BaseModel):
    """Analysis of an existing codebase."""
    total_files: int = 0
    file_types: dict[str, int] = {}
    detected_stack: dict[str, str] = {}
    has_claude_md: bool = False
    claude_md_content: str | None = None
    has_readme: bool = False
    readme_content: str | None = None
    directories: list[str] = []
    entry_points: list[str] = []
    project_name: str | None = None

class ProjectImportResponse(BaseModel):
    """Response for project import."""
    project: ProjectSummary
    analysis: CodebaseAnalysis
    is_import: bool = True


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

class ProjectPromptsUpdate(BaseModel):
    """Request schema for updating project prompts."""
    app_spec: str | None = None
    initializer_prompt: str | None = None
    coding_prompt: str | None = None

class ProjectImport(BaseModel):
    """Request schema for importing an existing project."""
    name: str = Field(..., min_length=1, max_length=50, pattern=r'^[a-zA-Z0-9_-]+$')
    path: str = Field(..., min_length=1, description="Absolute path to existing project")

class CodebaseAnalysis(BaseModel):
    """Analysis of an existing codebase."""
    total_files: int = 0
    file_types: dict[str, int] = {}
    detected_stack: dict[str, str] = {}
    has_claude_md: bool = False
    claude_md_content: str | None = None
    has_readme: bool = False
    readme_content: str | None = None
    directories: list[str] = []
    entry_points: list[str] = []
    project_name: str | None = None

class ProjectImportResponse(BaseModel):
    """Response for project import."""
    project: ProjectSummary
    analysis: CodebaseAnalysis
    is_import: bool = True

"""
Pydantic Schemas
================

Request/Response models for the API endpoints.
"""

import base64
from datetime import datetime
from typing import Literal

from pydantic import BaseModel, Field, field_validator

# ============================================================================
# Project Schemas
# ============================================================================

class ProjectCreate(BaseModel):
    """Request schema for creating a new project."""
    name: str = Field(..., min_length=1, max_length=50, pattern=r'^[a-zA-Z0-9_-]+$')
    path: str = Field(..., min_length=1, description="Absolute path to project directory")
    spec_method: Literal["claude", "manual"] = "claude"

class ProjectStats(BaseModel):
    """Project statistics."""
    passing: int = 0
    in_progress: int = 0
    total: int = 0
    percentage: float = 0.0

class ProjectSummary(BaseModel):
    """Summary of a project for list view."""
    name: str
    path: str
    has_spec: bool
    stats: ProjectStats

class ProjectDetail(BaseModel):
    """Detailed project information."""
    name: str
    path: str
    has_spec: bool
    stats: ProjectStats
    prompts_dir: str

class ProjectPrompts(BaseModel):
    """Project prompt files content."""
    app_spec: str = ""
    initializer_prompt: str = ""
    coding_prompt: str = ""

class ProjectPromptsUpdate(BaseModel):
    """Request schema for updating project prompts."""
    app_spec: str | None = None
    initializer_prompt: str | None = None
    coding_prompt: str | None = None

# ============================================================================
# Feature Schemas
# ============================================================================

class FeatureBase(BaseModel):
    """Base feature attributes."""
    category: str
    name: str
    description: str
    steps: list[str]

class FeatureCreate(FeatureBase):
    """Request schema for creating a new feature."""
    priority: int | None = None

class FeatureResponse(FeatureBase):
    """Response schema for a feature."""
    id: int
    priority: int
    passes: bool
    in_progress: bool

    class Config:
        from_attributes = True

class FeatureListResponse(BaseModel):
    """Response containing list of features organized by status."""
    pending: list[FeatureResponse]
    in_progress: list[FeatureResponse]
    done: list[FeatureResponse]

# ============================================================================
# Agent Schemas
# ============================================================================

class AgentStartRequest(BaseModel):
    """Request schema for starting the agent."""
    yolo_mode: bool = False

class AgentStatus(BaseModel):
    """Current agent status."""
    status: Literal["stopped", "running", "paused", "crashed"]
    pid: int | None = None
    started_at: datetime | None = None
    yolo_mode: bool = False

class AgentActionResponse(BaseModel):
    """Response for agent control actions."""
    success: bool
    status: str
    message: str = ""

# ============================================================================
# Setup Schemas
# ============================================================================

class SetupStatus(BaseModel):
    """System setup status."""
    claude_cli: bool
    credentials: bool
    node: bool
    npm: bool

# ============================================================================
# WebSocket Message Schemas
# ============================================================================

class WSProgressMessage(BaseModel):
    """WebSocket message for progress updates."""
    type: Literal["progress"] = "progress"
    passing: int
    total: int
    percentage: float

class WSFeatureUpdateMessage(BaseModel):
    """WebSocket message for feature status updates."""
    type: Literal["feature_update"] = "feature_update"
    feature_id: int
    passes: bool

class WSLogMessage(BaseModel):
    """WebSocket message for agent log output."""
    type: Literal["log"] = "log"
    line: str
    timestamp: datetime

class WSAgentStatusMessage(BaseModel):
    """WebSocket message for agent status changes."""
    type: Literal["agent_status"] = "agent_status"
    status: str

# ============================================================================
# Spec Chat Schemas
# ============================================================================

# Maximum image file size: 5 MB
MAX_IMAGE_SIZE = 5 * 1024 * 1024

class ImageAttachment(BaseModel):
    """Image attachment from client for spec creation chat."""
    filename: str = Field(..., min_length=1, max_length=255)
    mimeType: Literal['image/jpeg', 'image/png']
    base64Data: str

    @field_validator('base64Data')
    @classmethod
    def validate_base64_and_size(cls, v: str) -> str:
        """Validate that base64 data is valid and within size limit."""
        try:
            decoded = base64.b64decode(v)
            if len(decoded) > MAX_IMAGE_SIZE:
                raise ValueError(
                    f'Image size ({len(decoded) / (1024 * 1024):.1f} MB) exceeds '
                    f'maximum of {MAX_IMAGE_SIZE // (1024 * 1024)} MB'
                )
            return v
        except Exception as e:
            if 'Image size' in str(e):
                raise
            raise ValueError(f'Invalid base64 data: {e}')

# ============================================================================
# Filesystem Schemas
# ============================================================================

class DriveInfo(BaseModel):
    """Information about a drive (Windows only)."""
    letter: str
    label: str
    available: bool = True

class DirectoryEntry(BaseModel):
    """An entry in a directory listing."""
    name: str
    path: str  # POSIX format
    is_directory: bool
    is_hidden: bool = False
    size: int | None = None  # Bytes, for files
    has_children: bool = False  # True if directory has subdirectories

class DirectoryListResponse(BaseModel):
    """Response for directory listing."""
    current_path: str  # POSIX format
    parent_path: str | None
    entries: list[DirectoryEntry]
    drives: list[DriveInfo] | None = None  # Windows only

class PathValidationResponse(BaseModel):
    """Response for path validation."""
    valid: bool
    exists: bool
    is_directory: bool
    can_read: bool
    can_write: bool
    message: str = ""

class CreateDirectoryRequest(BaseModel):
    """Request to create a new directory."""
    parent_path: str
    name: str = Field(..., min_length=1, max_length=255)

# ============================================================================
# Reference Folder Schemas
# ============================================================================

class ReferenceFolderCreate(BaseModel):
    """Request schema for adding a reference folder."""
    path: str = Field(..., min_length=1, description="Absolute path to reference folder")
    alias: str | None = Field(None, max_length=100, description="Optional friendly name")
    description: str | None = Field(None, max_length=500, description="What this reference is for")

class ReferenceFolderUpdate(BaseModel):
    """Request schema for updating a reference folder."""
    alias: str | None = Field(None, max_length=100)
    description: str | None = Field(None, max_length=500)

class ReferenceFolderResponse(BaseModel):
    """Response schema for a reference folder."""
    id: int
    path: str
    alias: str | None
    description: str | None
    created_at: str | None
    exists: bool  # Whether the path still exists on disk

+    """Request schema for importing an existing project."""

+    name: str = Field(..., min_length=1, max_length=50, pattern=r'^[a-zA-Z0-9_-]+$')

+    path: str = Field(..., min_length=1, description="Absolute path to existing project")

+    """Analysis of an existing codebase."""

+    detected_stack: dict[str, str] = {}

+    claude_md_content: str | None = None


Now let me add the import endpoint to the projects router.


Now add the import endpoint:


from ..schemas import (
    ProjectCreate,
    ProjectDetail,
    ProjectPrompts,
    ProjectPromptsUpdate,
    ProjectStats,
    ProjectSummary,
)

from ..schemas import (
    ProjectCreate,
    ProjectDetail,
    ProjectPrompts,
    ProjectPromptsUpdate,
    ProjectStats,
    ProjectSummary,
    ProjectImport,
    ProjectImportResponse,
    CodebaseAnalysis,
)


from ..schemas import (
    ProjectCreate,
    ProjectDetail,
    ProjectPrompts,
    ProjectPromptsUpdate,
    ProjectStats,
    ProjectSummary,
    ProjectImport,
    ProjectImportResponse,
    CodebaseAnalysis,
)

"""
Projects Router
===============

API endpoints for project management.
Uses project registry for path lookups instead of fixed generations/ directory.
"""

import re
import shutil
from pathlib import Path

from fastapi import APIRouter, HTTPException

from ..schemas import (
    ProjectCreate,
    ProjectDetail,
    ProjectPrompts,
    ProjectPromptsUpdate,
    ProjectStats,
    ProjectSummary,
)

# Lazy imports to avoid circular dependencies
_imports_initialized = False
_check_spec_exists = None
_scaffold_project_prompts = None
_get_project_prompts_dir = None
_count_passing_tests = None

def _init_imports():
    """Lazy import of project-level modules."""
    global _imports_initialized, _check_spec_exists
    global _scaffold_project_prompts, _get_project_prompts_dir
    global _count_passing_tests

    if _imports_initialized:
        return

    import sys
    root = Path(__file__).parent.parent.parent
    if str(root) not in sys.path:
        sys.path.insert(0, str(root))

    from progress import count_passing_tests
    from prompts import get_project_prompts_dir, scaffold_project_prompts
    from start import check_spec_exists

    _check_spec_exists = check_spec_exists
    _scaffold_project_prompts = scaffold_project_prompts
    _get_project_prompts_dir = get_project_prompts_dir
    _count_passing_tests = count_passing_tests
    _imports_initialized = True

def _get_registry_functions():
    """Get registry functions with lazy import."""
    import sys
    root = Path(__file__).parent.parent.parent
    if str(root) not in sys.path:
        sys.path.insert(0, str(root))

    from registry import (
        get_project_path,
        list_registered_projects,
        register_project,
        unregister_project,
        validate_project_path,
    )
    return register_project, unregister_project, get_project_path, list_registered_projects, validate_project_path

def _get_reference_functions():
    """Get reference folder functions with lazy import."""
    import sys
    root = Path(__file__).parent.parent.parent
    if str(root) not in sys.path:
        sys.path.insert(0, str(root))

    from registry import (
        add_reference_folder,
        remove_reference_folder,
        list_reference_folders,
        get_reference_folder,
        update_reference_folder,
    )
    return add_reference_folder, remove_reference_folder, list_reference_folders, get_reference_folder, update_reference_folder

router = APIRouter(prefix="/api/projects", tags=["projects"])

def validate_project_name(name: str) -> str:
    """Validate and sanitize project name to prevent path traversal."""
    if not re.match(r'^[a-zA-Z0-9_-]{1,50}$', name):
        raise HTTPException(
            status_code=400,
            detail="Invalid project name. Use only letters, numbers, hyphens, and underscores (1-50 chars)."
        )
    return name

def get_project_stats(project_dir: Path) -> ProjectStats:
    """Get statistics for a project."""
    _init_imports()
    passing, in_progress, total = _count_passing_tests(project_dir)
    percentage = (passing / total * 100) if total > 0 else 0.0
    return ProjectStats(
        passing=passing,
        in_progress=in_progress,
        total=total,
        percentage=round(percentage, 1)
    )

@router.get("", response_model=list[ProjectSummary])
async def list_projects():
    """List all registered projects."""
    _init_imports()
    _, _, _, list_registered_projects, validate_project_path = _get_registry_functions()

    projects = list_registered_projects()
    result = []

    for name, info in projects.items():
        project_dir = Path(info["path"])

        # Skip if path no longer exists
        is_valid, _ = validate_project_path(project_dir)
        if not is_valid:
            continue

        has_spec = _check_spec_exists(project_dir)
        stats = get_project_stats(project_dir)

        result.append(ProjectSummary(
            name=name,
            path=info["path"],
            has_spec=has_spec,
            stats=stats,
        ))

    return result

@router.post("", response_model=ProjectSummary)
async def create_project(project: ProjectCreate):
    """Create a new project at the specified path."""
    _init_imports()
    register_project, _, get_project_path, _, _ = _get_registry_functions()

    name = validate_project_name(project.name)
    project_path = Path(project.path).resolve()

    # Check if project name already registered
    existing = get_project_path(name)
    if existing:
        raise HTTPException(
            status_code=409,
            detail=f"Project '{name}' already exists at {existing}"
        )

    # Security: Check if path is in a blocked location
    from .filesystem import is_path_blocked
    if is_path_blocked(project_path):
        raise HTTPException(
            status_code=403,
            detail="Cannot create project in system or sensitive directory"
        )

    # Validate the path is usable
    if project_path.exists():
        if not project_path.is_dir():
            raise HTTPException(
                status_code=400,
                detail="Path exists but is not a directory"
            )
    else:
        # Create the directory
        try:
            project_path.mkdir(parents=True, exist_ok=True)
        except OSError as e:
            raise HTTPException(
                status_code=500,
                detail=f"Failed to create directory: {e}"
            )

    # Scaffold prompts
    _scaffold_project_prompts(project_path)

    # Register in registry
    try:
        register_project(name, project_path)
    except Exception as e:
        raise HTTPException(
            status_code=500,
            detail=f"Failed to register project: {e}"
        )

    return ProjectSummary(
        name=name,
        path=project_path.as_posix(),
        has_spec=False,  # Just created, no spec yet
        stats=ProjectStats(passing=0, total=0, percentage=0.0),
    )

@router.get("/{name}", response_model=ProjectDetail)
async def get_project(name: str):
    """Get detailed information about a project."""
    _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 in registry")

    if not project_dir.exists():
        raise HTTPException(status_code=404, detail=f"Project directory no longer exists: {project_dir}")

    has_spec = _check_spec_exists(project_dir)
    stats = get_project_stats(project_dir)
    prompts_dir = _get_project_prompts_dir(project_dir)

    return ProjectDetail(
        name=name,
        path=project_dir.as_posix(),
        has_spec=has_spec,
        stats=stats,
        prompts_dir=str(prompts_dir),
    )

@router.delete("/{name}")
async def delete_project(name: str, delete_files: bool = False):
    """
    Delete a project from the registry.

    Args:
        name: Project name to delete
        delete_files: If True, also delete the project directory and files
    """
    _init_imports()
    _, unregister_project, 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")

    # Check if agent is running (verify process is actually alive, not just stale lock)
    lock_file = project_dir / ".agent.lock"
    if lock_file.exists():
        try:
            pid_str = lock_file.read_text().strip()
            if pid_str:
                import psutil
                pid = int(pid_str)
                if psutil.pid_exists(pid):
                    # Process exists - check if it's actually our agent
                    try:
                        proc = psutil.Process(pid)
                        if proc.is_running():
                            raise HTTPException(
                                status_code=409,
                                detail="Cannot delete project while agent is running. Stop the agent first."
                            )
                    except psutil.NoSuchProcess:
                        pass  # Process gone, stale lock
                # PID doesn't exist or process not running - clean up stale lock
                lock_file.unlink()
        except (ValueError, OSError):
            # Invalid lock file content or can't read - clean it up
            try:
                lock_file.unlink()
            except OSError:
                pass

    # Optionally delete files
    if delete_files and project_dir.exists():
        try:
            shutil.rmtree(project_dir)
        except Exception as e:
            raise HTTPException(status_code=500, detail=f"Failed to delete project files: {e}")

    # Unregister from registry
    unregister_project(name)

    return {
        "success": True,
        "message": f"Project '{name}' deleted" + (" (files removed)" if delete_files else " (files preserved)")
    }

@router.get("/{name}/prompts", response_model=ProjectPrompts)
async def get_project_prompts(name: str):
    """Get the content of project prompt files."""
    _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")

    prompts_dir = _get_project_prompts_dir(project_dir)

    def read_file(filename: str) -> str:
        filepath = prompts_dir / filename
        if filepath.exists():
            try:
                return filepath.read_text(encoding="utf-8")
            except Exception:
                return ""
        return ""

    return ProjectPrompts(
        app_spec=read_file("app_spec.txt"),
        initializer_prompt=read_file("initializer_prompt.md"),
        coding_prompt=read_file("coding_prompt.md"),
    )

@router.put("/{name}/prompts")
async def update_project_prompts(name: str, prompts: ProjectPromptsUpdate):
    """Update project prompt files."""
    _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")

    prompts_dir = _get_project_prompts_dir(project_dir)
    prompts_dir.mkdir(parents=True, exist_ok=True)

    def write_file(filename: str, content: str | None):
        if content is not None:
            filepath = prompts_dir / filename
            filepath.write_text(content, encoding="utf-8")

    write_file("app_spec.txt", prompts.app_spec)
    write_file("initializer_prompt.md", prompts.initializer_prompt)
    write_file("coding_prompt.md", prompts.coding_prompt)

    return {"success": True, "message": "Prompts updated"}

@router.get("/{name}/stats", response_model=ProjectStats)
async def get_project_stats_endpoint(name: str):
    """Get current progress statistics for a project."""
    _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")

    return get_project_stats(project_dir)

# ============================================================================
# Reference Folder Endpoints
# ============================================================================

from ..schemas import ReferenceFolderCreate, ReferenceFolderUpdate, ReferenceFolderResponse

@router.get("/{name}/references", response_model=list[ReferenceFolderResponse])
async def list_references(name: str):
    """List all reference folders for a project."""
    _, _, get_project_path, _, _ = _get_registry_functions()
    _, _, list_reference_folders, _, _ = _get_reference_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")

    refs = list_reference_folders(name)
    return [ReferenceFolderResponse(**r) for r in refs]

@router.post("/{name}/references", response_model=ReferenceFolderResponse)
async def add_reference(name: str, request: ReferenceFolderCreate):
    """Add a reference folder to a project."""
    _, _, get_project_path, _, _ = _get_registry_functions()
    add_reference_folder, _, list_reference_folders, get_reference_folder, _ = _get_reference_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")

    # Security check: don't allow system paths
    from .filesystem import is_path_blocked
    ref_path = Path(request.path).resolve()
    if is_path_blocked(ref_path):
        raise HTTPException(
            status_code=403,
            detail="Cannot add reference to system or sensitive directory"
        )

    try:
        ref_id = add_reference_folder(
            project_name=name,
            path=ref_path,
            alias=request.alias,
            description=request.description,
        )
    except ValueError as e:
        raise HTTPException(status_code=400, detail=str(e))
    except Exception as e:
        raise HTTPException(status_code=409, detail=str(e))

    ref = get_reference_folder(ref_id)
    return ReferenceFolderResponse(**ref)

@router.delete("/{name}/references/{ref_id}")
async def remove_reference(name: str, ref_id: int):
    """Remove a reference folder from a project."""
    _, _, get_project_path, _, _ = _get_registry_functions()
    _, remove_reference_folder, _, get_reference_folder, _ = _get_reference_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")

    # Verify the reference belongs to this project
    ref = get_reference_folder(ref_id)
    if not ref or ref.get("project_name") != name:
        raise HTTPException(status_code=404, detail="Reference folder not found")

    remove_reference_folder(ref_id)
    return {"success": True, "message": "Reference folder removed"}

@router.patch("/{name}/references/{ref_id}", response_model=ReferenceFolderResponse)
async def update_reference(name: str, ref_id: int, request: ReferenceFolderUpdate):
    """Update a reference folder's metadata."""
    _, _, get_project_path, _, _ = _get_registry_functions()
    _, _, _, get_reference_folder, update_reference_folder = _get_reference_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")

    # Verify the reference belongs to this project
    ref = get_reference_folder(ref_id)
    if not ref or ref.get("project_name") != name:
        raise HTTPException(status_code=404, detail="Reference folder not found")

    update_reference_folder(
        reference_id=ref_id,
        alias=request.alias,
        description=request.description,
    )

    ref = get_reference_folder(ref_id)
    return ReferenceFolderResponse(**ref)


Now let me add the import endpoint itself. I'll add it right after the create_project endpoint.