[Design] Add create-project-from-scratch flow - #325
[Design] Add create-project-from-scratch flow#325Julia Shilova (juliashilovaa) wants to merge 13 commits into
Conversation
| import { expect, test } from "@playwright/test"; | ||
| import { defaultWorkspaceRow, openAppFresh } from "./fixtures/app"; | ||
|
|
||
| // The agent-led interview + finalize_project step is @agent-tier; this spec covers the |
There was a problem hiding this comment.
Non-blocking:
Problem: This new multi-line rationale and the later narrative comments violate the repository rule that code comments stay near-zero and design/coverage decisions live in the owning SPEC.md.
Failure scenario: The authoritative e2e spec omits the agent-tier coverage boundary while the test file becomes a second, non-authoritative explanation of it.
Suggested fix: Move the coverage rationale into e2e/SPEC.md and remove the narrative comments from this test.
…-from-scratch Add a Project.draft lifecycle flag and the server-owned primitives behind an agent-led 'create project from scratch' flow: - contracts: Project.draft?: true; project.create / project.finalize / project.discardDraft wire methods; PROTOCOL_VERSION 51. - projects: createDraftProject (managed dir under <dataDir>/projects/<uuid> + initProject + provisional name), finalizeProject / finalizeProjectByPath (apply confirmed name, uniquify slug, clear draft), discardDraftProject (abandon/recovery; refuses non-draft, guarded rm under the managed root). - agent: finalize_project pi tool (keys on ctx.cwd, fails closed off a draft) + setProjectFinalizeHandler seam wired in host to projects. - host: project.create/finalize/discardDraft handlers + teardown. Reuses initProject and the Default workspace; no new project type.
…oject When the finalize_project tool is present (a fresh draft project's setup chat), open by asking what to build, keep clarifications minimal via ask_user_question, ask the project name LAST, write goal-and-requirements.md, then call finalize_project and give the contextual ready message. Inert without the tool (the setting-up-a-project dispatcher in a real repo is unchanged).
- AddProjectMenu: 'New project from scratch' beside 'Open existing project' (rail + Welcome). - WelcomePanel: a first-class 'Create project from scratch' card in the no-project state (Open project not demoted). - createProject.ts: shared createProjectFromScratch (project.create -> draft project + Default workspace -> chat seeded with /skill:starting-a-new-project). - chat/tools: FinalizeProjectCard receipt for the finalize_project tool. - e2e: no-agent coverage of the entry points + draft creation; resetState clears the managed projects dir.
- Open (existing) project uses FolderOpen (folder-open) in the add-project menu. - Create project from scratch uses FolderPlus (folder-add) in the menu + the Welcome card.
A not-yet-finalized project shows as "Project draft" (was "Untitled project") until the create-from-scratch flow finalizes it with the user-confirmed name.
…from-scratch Refine the hosted create-from-scratch entry in starting-a-new-project: - Open with a fixed, extremely concise 3-question brief (what / who / what can users do) as ordinary freeform chat — no widget, no required fields, no spec/git/workspace mentions. - After the user's single freeform reply, generate CONTEXTUAL ask_user_question follow-ups from what they actually said (never a preset list; never re-ask; accumulate all prior answers; group related decisions; minimum rounds). - Name the project last via the widget (suggest one; reuse a user-given name). - Unchanged tail: goal-and-requirements.md + finalize_project + ready message.
Right-clicking a draft project now offers a destructive "Delete draft" (instead of "Close project") with a confirm popup that discards the drafts
Post-creation branching capability: - offerNextSteps.ts: offer_next_steps tool (ack+terminate like ask_user_question) + buildNextStepsMessage. Answered through the shared answerQuestion path: assessAnswerability now takes an allowed tool-name set and returns the matched name; the manager dispatches buildNextStepsMessage (offer) vs buildAnswersMessage (ask). Registered in sharedFactories. - projects.finalize now commits goal-and-requirements.md (if present) so a later isolated worktree inherits the project brief; a no-op/failed commit is ignored. - Unit tests for the offer tool + generalized answerability.
…ace skill - starting-a-new-project hosted-entry tail: after finalize, post a concise ready confirmation, then offer_next_steps (two cards). On "Continue in the Default workspace", ack + one contextual next-step question via the widget; on "Start a separate task", acknowledge briefly (the app opens the new workspace). - New kicking-off-a-workspace skill (self-trigger via /skill: seed): warm hand-off into a freshly created isolated workspace — read the committed brief, offer a few first tasks via the questionnaire widget, then route to brainstorming. Added to the family table + diagram (outside routing).
- NextStepsCard renderer for offer_next_steps (chrome "bare"): two action cards
("Start a separate task" / "Continue in the Default workspace", no git terms),
a one-time decision resolved via the transcript (deriveAskStates recognizes
offer_next_steps). Continue answers the tool; Separate task answers + calls
ChatActions.startSeparateTask().
- ChatActions.startSeparateTask (implemented by ChatView) fires the new
newWorkspaceRequest store seam; ProjectTree consumes it to open the existing
NewWorkspaceDialog, seeded with /skill:kicking-off-a-workspace when kickoff.
- Store: newWorkspaceRequest field + requestNewWorkspace/clearNewWorkspaceRequest
(+ unit test). SPEC updates (chat, chat/tools, store, panels).
…roject sessions
Blocker: these create-from-scratch tools were registered globally in
buildResourceLoader (every session), relying on a fail-closed handler as the
gate. Restore them as a draft-only capability driven by an explicit host->agent
seam, so ordinary sessions never see them.
- agent: setDraftProjectSetupResolver(workspaceId) seam; createSession and the
re-attach path pass { draftProjectSetup } into buildResourceLoader, which adds
finalize_project + offer_next_steps ONLY when set. Captured at create/attach
time, so it stays true for the setup session's life (offer_next_steps runs
after finalize clears the draft). finalize_project's cwd draft check stays as
defense-in-depth, not the gate.
- projects: isDraftProject(id); host wires the resolver (workspaceId -> project
draft state). agent still never imports projects.
- Regression tests: draft session -> both tools present; ordinary session ->
both absent while ask_user_question (the normal setup interview) is unaffected.
The reused starting-a-new-project flow for normal empty repos is unchanged (its
hosted-entry section is inert without the tool). No user-facing flow change.
…Draft Blocker: the handler tore down workspace resources (layouts, reviews, watches, terminals, sessions) first and only then called discardDraftProject, which rejects a non-draft. A stale client discarding an already-finalized project thus destroyed its resources before the request failed. - discardDraftProject(id, teardown?) now runs the draft guard FIRST — throws on a missing id or a non-draft project — and only after it passes runs the caller's teardown callback, then removes the record/dir and publishes project.removed. Guard + teardown + removal live in one op, so there is no path where teardown runs for a non-draft; the host handler holds no draft logic of its own. - Handler passes its workspace-resource teardown as that callback. - Reuses the existing draft domain check (no duplicated draft logic). Regression tests: valid draft → discard succeeds and teardown runs; finalized project → rejected, teardown NOT run, project + dir untouched; missing project → rejected, teardown NOT run. No user-facing flow change.
a8d3f93 to
8a12534
Compare
| if (!project) throw new Error(`Unknown project: ${id}`); | ||
| if (project.draft !== true) throw new Error(`Project is not a draft: ${project.name}`); | ||
| // Draft guard has passed: only now may any destructive teardown run (see projects/SPEC.md). | ||
| if (teardown) await teardown(project); |
There was a problem hiding this comment.
Blocking:
Problem: The draft check is not atomic with deletion because this awaited teardown yields without reserving the lifecycle state, so project.finalize can still succeed before removal resumes.
Failure scenario: One client confirms Delete draft while session abort/purge is pending, another client finalizes that same project, and this call then resumes and removes the newly finalized project's directory and records.
Suggested fix: Serialize finalize/discard per project, or synchronously transition the draft to a persisted discarding state before the first await so finalize rejects, rolling that state back if teardown fails.
| settingsManager, | ||
| () => skillAdmissionResolver(input.workspaceId), | ||
| generation.excludedSessionExtensionPaths, | ||
| { draftProjectSetup: draftProjectSetupResolver(input.workspaceId) }, |
There was a problem hiding this comment.
Blocking:
Problem: This gate identifies every session in a draft project as the one setup session, even though drafts still expose New chat and Create workspace.
Failure scenario: While the setup chat is waiting, opening another supported chat in the draft gives it finalize_project and offer_next_steps; invoking the starting-project skill there can finalize first, after which the original setup flow fails and cannot complete.
Suggested fix: Bind the draft capability to an explicit, host-validated setup-session identity passed by the create-project flow, and persist that identity for reattachment instead of deriving it only from the workspace's project.
Summary
project-from-scratch.mov