diff --git a/docs/api/goals-and-projects.md b/docs/api/goals-and-projects.md index 5644e92b..52c95223 100644 --- a/docs/api/goals-and-projects.md +++ b/docs/api/goals-and-projects.md @@ -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" } ``` diff --git a/server/src/routes/projects.ts b/server/src/routes/projects.ts index 1785b1dd..41bf9ace 100644 --- a/server/src/routes/projects.ts +++ b/server/src/routes/projects.ts @@ -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 & { - workspace?: Record; + type CreateProjectPayload = Parameters[1] & { + workspace?: Parameters[1]; }; + + const { workspace, ...projectData } = req.body as CreateProjectPayload; const project = await svc.create(companyId, projectData); let createdWorkspaceId: string | null = null; if (workspace) { diff --git a/skills/paperclip/SKILL.md b/skills/paperclip/SKILL.md index 59d82229..de0bed25 100644 --- a/skills/paperclip/SKILL.md +++ b/skills/paperclip/SKILL.md @@ -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` | diff --git a/skills/paperclip/references/api-reference.md b/skills/paperclip/references/api-reference.md index 4a562ddd..08d0fc63 100644 --- a/skills/paperclip/references/api-reference.md +++ b/skills/paperclip/references/api-reference.md @@ -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 | diff --git a/ui/src/components/ProjectProperties.tsx b/ui/src/components/ProjectProperties.tsx index 1116ed95..af11a26c 100644 --- a/ui/src/components/ProjectProperties.tsx +++ b/ui/src/components/ProjectProperties.tsx @@ -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 }) => + 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 (
@@ -285,13 +325,8 @@ export function ProjectProperties({ project, onUpdate }: ProjectPropertiesProps) @@ -312,13 +347,8 @@ export function ProjectProperties({ project, onUpdate }: ProjectPropertiesProps) @@ -427,6 +457,9 @@ export function ProjectProperties({ project, onUpdate }: ProjectPropertiesProps) {removeWorkspace.isError && (

Failed to delete workspace.

)} + {updateWorkspace.isError && ( +

Failed to update workspace.

+ )}