feat(plan): add Plan tab as the first project surface
A new home for everything that happens BEFORE building: - Vision — one-line elevator pitch (mirrors productVision) - Ideas — the "park-it" bin for raw thoughts - Tasks — what needs to happen next (open / done) - Decisions — log of "we chose X over Y because Z" Storage is appended under fs_projects.data.plan so no schema migration is needed. CRUD lives at /api/projects/[projectId]/plan. The bare project URL now redirects to /plan instead of /product, and the AI chat receives decisions + open tasks in its active-project context block — so it stops re-litigating settled questions and knows what's queued up. Made-with: Cursor
This commit is contained in:
@@ -74,6 +74,32 @@ export function buildSystemPrompt(
|
||||
// at the top so the model treats `projectId` as resolved without the
|
||||
// user having to name it. Falls through to the workspace-level mode
|
||||
// (browse all projects) when activeProject is undefined.
|
||||
// Pull plan artifacts (decisions + open tasks) so the AI doesn't ask
|
||||
// the user to re-decide settled questions and knows what's queued up.
|
||||
// Decisions are first-class: they encode the founder's intent and
|
||||
// should be honored unless the user explicitly revisits one.
|
||||
const plan = (activeProject?.plan ?? {}) as {
|
||||
decisions?: { title: string; choice: string; why?: string }[];
|
||||
tasks?: { text: string; status: "open" | "done" }[];
|
||||
ideas?: { text: string }[];
|
||||
};
|
||||
const decisionsBlock = plan.decisions?.length
|
||||
? `\n**Decisions already made for this project (DO NOT re-litigate unless the user asks):**\n${plan.decisions
|
||||
.slice(0, 20)
|
||||
.map((d) => `- ${d.title} → ${d.choice}${d.why ? ` (because: ${d.why})` : ''}`)
|
||||
.join('\n')}\n`
|
||||
: '';
|
||||
const openTasks = (plan.tasks ?? []).filter((t) => t.status === 'open').slice(0, 15);
|
||||
const tasksBlock = openTasks.length
|
||||
? `\n**Open tasks the user has captured:**\n${openTasks.map((t) => `- ${t.text}`).join('\n')}\n`
|
||||
: '';
|
||||
const ideasBlock = plan.ideas?.length
|
||||
? `\n**Ideas parked (not commitments — surface only if relevant):**\n${plan.ideas
|
||||
.slice(0, 10)
|
||||
.map((i) => `- ${i.text}`)
|
||||
.join('\n')}\n`
|
||||
: '';
|
||||
|
||||
const activeBlock = activeProject
|
||||
? `\n## ACTIVE PROJECT — assume this for every tool call unless the user explicitly says otherwise
|
||||
|
||||
@@ -84,7 +110,7 @@ The user is currently looking at:
|
||||
- Audience: ${activeProject.audience ?? 'unspecified'}
|
||||
- Vision: ${activeProject.productVision ? activeProject.productVision.slice(0, 240) : '(not yet captured)'}
|
||||
${activeProject.kickoff ? `- Created via: ${activeProject.kickoff.mode} (${JSON.stringify(activeProject.kickoff.sourceData).slice(0, 200)})` : ''}
|
||||
|
||||
${decisionsBlock}${tasksBlock}${ideasBlock}
|
||||
When you call tools that take a \`projectId\`, USE this id (\`${activeProject.id}\`) without asking. When the user says "this project" / "the app" / "deploy it" — they mean THIS project. Switch to a different project only if the user names one explicitly.\n`
|
||||
: '';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user