173 lines
5.7 KiB
TypeScript
173 lines
5.7 KiB
TypeScript
import { NextResponse } from "next/server";
|
|
import { authSession } from "@/lib/auth/session-server";
|
|
import { query, queryOne } from "@/lib/db-postgres";
|
|
import { callVibnChat } from "@/lib/ai/vibn-chat-model";
|
|
|
|
// Generate a complete PRD, Tech Architecture, and Execution Plan based on the objective
|
|
|
|
export async function POST(
|
|
req: Request,
|
|
ctx: { params: Promise<{ projectId: string }> }
|
|
) {
|
|
const { projectId } = await ctx.params;
|
|
const session = await authSession();
|
|
if (!session?.user?.email) {
|
|
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
}
|
|
|
|
const projectRow = await queryOne<{ data: any; slug: string }>(
|
|
`SELECT data, slug FROM fs_projects
|
|
JOIN fs_users u ON u.id = fs_projects.user_id
|
|
WHERE fs_projects.id = $1 AND u.data->>'email' = $2 LIMIT 1`,
|
|
[projectId, session.user.email]
|
|
);
|
|
if (!projectRow) return NextResponse.json({ error: "Project not found" }, { status: 404 });
|
|
|
|
const body = await req.json().catch(() => ({}));
|
|
const objective = String(body.objective || projectRow.data?.plan?.vision || "").trim();
|
|
|
|
if (!objective) {
|
|
return NextResponse.json({ error: "Objective is required to generate a PRD." }, { status: 400 });
|
|
}
|
|
|
|
// We run three sequential LLM calls to construct the complete Spec Kit documentation
|
|
|
|
// 1. Generate the Product Spec (User Stories & Requirements)
|
|
const specPrompt = `You are a Principal Product Manager writing a Product Requirements Document (PRD).
|
|
Based on the following Objective, generate a structured PRD using Markdown.
|
|
DO NOT talk about technology (databases, frameworks, APIs). Focus purely on user value and capabilities.
|
|
|
|
OBJECTIVE:
|
|
${objective}
|
|
|
|
You MUST follow this exact structure:
|
|
|
|
## User Scenarios & Testing
|
|
List the 3-5 most critical User Stories, prioritized.
|
|
### User Story 1 - [Brief Title] (Priority: P1)
|
|
[Description]
|
|
**Acceptance Scenarios**:
|
|
1. **Given** [state], **When** [action], **Then** [outcome]
|
|
|
|
### User Story 2 - [Brief Title] (Priority: P2)
|
|
[Repeat...]
|
|
|
|
## Functional Requirements
|
|
- **FR-001**: System MUST [capability]
|
|
- **FR-002**: Users MUST be able to [interaction]
|
|
|
|
## Success Criteria
|
|
- **SC-001**: [Measurable business outcome]`;
|
|
|
|
const specResponse = await callVibnChat({
|
|
systemPrompt: specPrompt,
|
|
messages: [{ role: "user", content: "Generate the PRD." }],
|
|
temperature: 0.2
|
|
});
|
|
const specMd = specResponse.text || "Failed to generate PRD.";
|
|
|
|
// 2. Generate the Technical Architecture (Plan)
|
|
const planPrompt = `You are a Principal Software Architect.
|
|
Based on the following PRD, define the Technical Architecture required to build it.
|
|
Your choices must default to: Next.js (App Router), Postgres, Tailwind CSS, NextAuth, and Stripe (if payments).
|
|
|
|
PRD:
|
|
${specMd}
|
|
|
|
You MUST follow this exact structure:
|
|
|
|
## Technical Context
|
|
- **Language/Framework**: Next.js 15 (App Router), TypeScript
|
|
- **Database**: Postgres (via Prisma or raw SQL)
|
|
- **Styling**: Tailwind CSS
|
|
- **Authentication**: [Decide auth strategy]
|
|
|
|
## Data Model
|
|
List the core database tables and their relationships.
|
|
- **[EntityName]**: [fields...]
|
|
|
|
## File Structure
|
|
Provide a high-level file tree representing the required Next.js pages and API routes.
|
|
\`\`\`text
|
|
src/app/
|
|
page.tsx
|
|
api/
|
|
\`\`\``;
|
|
|
|
const planResponse = await callVibnChat({
|
|
systemPrompt: planPrompt,
|
|
messages: [{ role: "user", content: "Generate the Technical Architecture." }],
|
|
temperature: 0.1
|
|
});
|
|
const planMd = planResponse.text || "Failed to generate Architecture.";
|
|
|
|
// 3. Generate the Execution Plan (Tasks)
|
|
const tasksPrompt = `You are a Technical Project Manager.
|
|
Based on the PRD and Technical Architecture, break the implementation down into an atomic, dependency-ordered Task List.
|
|
You MUST format each task EXACTLY as a markdown bullet with a checkbox, a phase tag, and a description containing the file paths.
|
|
|
|
PRD:
|
|
${specMd}
|
|
|
|
ARCHITECTURE:
|
|
${planMd}
|
|
|
|
You MUST follow this exact structure:
|
|
|
|
## Phase 1: Setup & Foundation
|
|
- [ ] [Phase 1] Initialize Next.js project and layout
|
|
- [ ] [Phase 1] Setup Postgres database schema
|
|
|
|
## Phase 2: [User Story 1 Title]
|
|
- [ ] [US1] Build API route for [feature]
|
|
- [ ] [US1] Build frontend UI component in src/app/[route]/page.tsx
|
|
|
|
## Phase 3: [User Story 2 Title]
|
|
- [ ] [US2] ...`;
|
|
|
|
const tasksResponse = await callVibnChat({
|
|
systemPrompt: tasksPrompt,
|
|
messages: [{ role: "user", content: "Generate the Execution Plan tasks." }],
|
|
temperature: 0.1
|
|
});
|
|
const tasksMd = tasksResponse.text || "";
|
|
|
|
// 4. Parse the tasks string into the JSON array expected by the DB
|
|
// Extract all `- [ ] [Group] Task description` lines
|
|
const taskLines = tasksMd.match(/- \[[ x]\] \[(.*?)\] (.*)/g) || [];
|
|
const parsedTasks = taskLines.map(line => {
|
|
const match = line.match(/- \[[ x]\] \[(.*?)\] (.*)/);
|
|
if (!match) return null;
|
|
return {
|
|
id: Math.random().toString(36).slice(2, 11),
|
|
title: `[${match[1]}] ${match[2]}`, // Fixed backticks
|
|
description: "",
|
|
status: "open",
|
|
createdAt: new Date().toISOString()
|
|
};
|
|
}).filter(Boolean);
|
|
|
|
// 5. Update the Database Plan
|
|
const currentPlan = projectRow.data?.plan || {};
|
|
|
|
// Save the PRD under decisions (we can render it specially in the UI)
|
|
const newDecisions = [
|
|
{ id: "prd_spec", title: "Product Specification", choice: "Auto-generated PRD", why: specMd },
|
|
{ id: "prd_arch", title: "Technical Architecture", choice: "Auto-generated Plan", why: planMd }
|
|
];
|
|
|
|
const updatedPlan = {
|
|
...currentPlan,
|
|
vision: objective,
|
|
tasks: parsedTasks.length > 0 ? parsedTasks : currentPlan.tasks,
|
|
decisions: newDecisions
|
|
};
|
|
|
|
await query(
|
|
`UPDATE fs_projects SET data = jsonb_set(data, '{plan}', $2::jsonb) WHERE id = $1`,
|
|
[projectId, JSON.stringify(updatedPlan)]
|
|
);
|
|
|
|
return NextResponse.json({ plan: updatedPlan });
|
|
}
|