feat: project workspace clear/update UX and creation docs

Add granular workspace management — clear local folder or repo URL
independently instead of deleting the whole workspace. Fix project
create route typing. Document inline workspace creation in API docs
and skill references.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dotta
2026-03-02 14:21:03 -06:00
parent f54f30cb90
commit ff472af343
5 changed files with 135 additions and 20 deletions

View File

@@ -70,17 +70,30 @@ POST /api/companies/{companyId}/projects
{
"name": "Auth System",
"description": "End-to-end authentication",
"goalId": "{goalId}",
"status": "active"
"goalIds": ["{goalId}"],
"status": "planned",
"workspace": {
"name": "auth-repo",
"cwd": "/path/to/workspace",
"repoUrl": "https://github.com/org/repo",
"repoRef": "main",
"isPrimary": true
}
}
```
Notes:
- `workspace` is optional. If present, the project is created and seeded with that workspace.
- A workspace must include at least one of `cwd` or `repoUrl`.
- For repo-only projects, omit `cwd` and provide `repoUrl`.
### Update Project
```
PATCH /api/projects/{projectId}
{
"status": "completed"
"status": "in_progress"
}
```

View File

@@ -35,9 +35,11 @@ export function projectRoutes(db: Db) {
router.post("/companies/:companyId/projects", validate(createProjectSchema), async (req, res) => {
const companyId = req.params.companyId as string;
assertCompanyAccess(req, companyId);
const { workspace, ...projectData } = req.body as Record<string, unknown> & {
workspace?: Record<string, unknown>;
type CreateProjectPayload = Parameters<typeof svc.create>[1] & {
workspace?: Parameters<typeof svc.createWorkspace>[1];
};
const { workspace, ...projectData } = req.body as CreateProjectPayload;
const project = await svc.create(companyId, projectData);
let createdWorkspaceId: string | null = null;
if (workspace) {

View File

@@ -75,6 +75,19 @@ Status values: `backlog`, `todo`, `in_progress`, `in_review`, `done`, `blocked`,
**Step 9 — Delegate if needed.** Create subtasks with `POST /api/companies/{companyId}/issues`. Always set `parentId` and `goalId`. Set `billingCode` for cross-team work.
## Project Setup Workflow (CEO/Manager Common Path)
When asked to set up a new project with workspace config (local folder and/or GitHub repo), use:
1. `POST /api/companies/{companyId}/projects` with project fields.
2. Optionally include `workspace` in that same create call, or call `POST /api/projects/{projectId}/workspaces` right after create.
Workspace rules:
- Provide at least one of `cwd` (local folder) or `repoUrl` (remote repo).
- For repo-only setup, omit `cwd` and provide `repoUrl`.
- Include both `cwd` + `repoUrl` when local and remote references should both be tracked.
## Critical Rules
- **Always checkout** before working. Never PATCH to `in_progress` manually.
@@ -152,6 +165,8 @@ pls show the costs in either token or dollars on the /issues/{id} page. Make a p
| Update task | `PATCH /api/issues/:issueId` (optional `comment` field) |
| Add comment | `POST /api/issues/:issueId/comments` |
| Create subtask | `POST /api/companies/:companyId/issues` |
| Create project | `POST /api/companies/:companyId/projects` |
| Create project workspace | `POST /api/projects/:projectId/workspaces` |
| Release task | `POST /api/issues/:issueId/release` |
| List agents | `GET /api/companies/:companyId/agents` |
| Dashboard | `GET /api/companies/:companyId/dashboard` |

View File

@@ -280,6 +280,58 @@ Use the dashboard for situational awareness, especially if you're a manager or C
---
## Project Setup (Create + Workspace)
When a CEO/manager task asks you to "set up a new project" and wire local + GitHub context, use this sequence.
### Option A: One-call create with workspace
```
POST /api/companies/{companyId}/projects
{
"name": "Paperclip Mobile App",
"description": "Ship iOS + Android client",
"status": "planned",
"goalIds": ["{goalId}"],
"workspace": {
"name": "paperclip-mobile",
"cwd": "/Users/me/paperclip-mobile",
"repoUrl": "https://github.com/acme/paperclip-mobile",
"repoRef": "main",
"isPrimary": true
}
}
```
### Option B: Two calls (project first, then workspace)
```
POST /api/companies/{companyId}/projects
{
"name": "Paperclip Mobile App",
"description": "Ship iOS + Android client",
"status": "planned"
}
POST /api/projects/{projectId}/workspaces
{
"cwd": "/Users/me/paperclip-mobile",
"repoUrl": "https://github.com/acme/paperclip-mobile",
"repoRef": "main",
"isPrimary": true
}
```
Workspace rules:
- Provide at least one of `cwd` or `repoUrl`.
- For repo-only setup, omit `cwd` and provide `repoUrl`.
- The first workspace is primary by default.
Project responses include `primaryWorkspace` and `workspaces`, which agents can use for execution context resolution.
---
## Governance and Approvals
Some actions require board approval. You cannot bypass these gates.
@@ -406,7 +458,7 @@ Terminal states: `done`, `cancelled`
| GET | `/api/companies/:companyId` | Company details |
| GET | `/api/companies/:companyId/projects` | List projects |
| GET | `/api/projects/:projectId` | Project details |
| POST | `/api/companies/:companyId/projects` | Create project |
| POST | `/api/companies/:companyId/projects` | Create project (optional inline `workspace`) |
| PATCH | `/api/projects/:projectId` | Update project |
| GET | `/api/projects/:projectId/workspaces` | List project workspaces |
| POST | `/api/projects/:projectId/workspaces` | Create project workspace |

View File

@@ -83,6 +83,11 @@ export function ProjectProperties({ project, onUpdate }: ProjectPropertiesProps)
mutationFn: (workspaceId: string) => projectsApi.removeWorkspace(project.id, workspaceId),
onSuccess: invalidateProject,
});
const updateWorkspace = useMutation({
mutationFn: ({ workspaceId, data }: { workspaceId: string; data: Record<string, unknown> }) =>
projectsApi.updateWorkspace(project.id, workspaceId, data),
onSuccess: invalidateProject,
});
const removeGoal = (goalId: string) => {
if (!onUpdate) return;
@@ -167,6 +172,41 @@ export function ProjectProperties({ project, onUpdate }: ProjectPropertiesProps)
});
};
const clearLocalWorkspace = (workspace: Project["workspaces"][number]) => {
const confirmed = window.confirm(
workspace.repoUrl
? "Clear local folder from this workspace?"
: "Delete this workspace local folder?",
);
if (!confirmed) return;
if (workspace.repoUrl) {
updateWorkspace.mutate({
workspaceId: workspace.id,
data: { cwd: null },
});
return;
}
removeWorkspace.mutate(workspace.id);
};
const clearRepoWorkspace = (workspace: Project["workspaces"][number]) => {
const hasLocalFolder = Boolean(workspace.cwd && workspace.cwd !== REPO_ONLY_CWD_SENTINEL);
const confirmed = window.confirm(
hasLocalFolder
? "Clear GitHub repo from this workspace?"
: "Delete this workspace repo?",
);
if (!confirmed) return;
if (hasLocalFolder) {
updateWorkspace.mutate({
workspaceId: workspace.id,
data: { repoUrl: null, repoRef: null },
});
return;
}
removeWorkspace.mutate(workspace.id);
};
return (
<div className="space-y-4">
<div className="space-y-1">
@@ -285,13 +325,8 @@ export function ProjectProperties({ project, onUpdate }: ProjectPropertiesProps)
<Button
variant="ghost"
size="icon-xs"
onClick={() => {
const confirmed = window.confirm("Delete this workspace?");
if (confirmed) {
removeWorkspace.mutate(workspace.id);
}
}}
aria-label="Delete workspace"
onClick={() => clearLocalWorkspace(workspace)}
aria-label="Delete local folder"
>
<Trash2 className="h-3 w-3" />
</Button>
@@ -312,13 +347,8 @@ export function ProjectProperties({ project, onUpdate }: ProjectPropertiesProps)
<Button
variant="ghost"
size="icon-xs"
onClick={() => {
const confirmed = window.confirm("Delete this workspace?");
if (confirmed) {
removeWorkspace.mutate(workspace.id);
}
}}
aria-label="Delete workspace"
onClick={() => clearRepoWorkspace(workspace)}
aria-label="Delete workspace repo"
>
<Trash2 className="h-3 w-3" />
</Button>
@@ -427,6 +457,9 @@ export function ProjectProperties({ project, onUpdate }: ProjectPropertiesProps)
{removeWorkspace.isError && (
<p className="text-xs text-destructive">Failed to delete workspace.</p>
)}
{updateWorkspace.isError && (
<p className="text-xs text-destructive">Failed to update workspace.</p>
)}
</div>
<Separator />