import { z } from 'zod'; import type { LlmClient } from '@/lib/ai/llm-client'; import { GeminiLlmClient } from '@/lib/ai/gemini-client'; import { clamp, nowIso, loadPhaseContainers, persistPhaseArtifacts } from '@/lib/server/projects'; import type { MvpPlan } from '@/lib/types/mvp'; const MvpPlanSchema = z.object({ projectId: z.string(), coreFlows: z.array(z.string()).default([]), coreFeatures: z.array(z.string()).default([]), supportingFeatures: z.array(z.string()).default([]), outOfScope: z.array(z.string()).default([]), technicalTasks: z.array(z.string()).default([]), blockers: z.array(z.string()).default([]), overallConfidence: z.number().min(0).max(1), }); export async function runMvpPlanning(projectId: string, llmClient?: LlmClient): Promise { const { phaseData } = await loadPhaseContainers(projectId); const canonical = phaseData.canonicalProductModel; if (!canonical) { throw new Error('Canonical product model missing. Run buildCanonicalProductModel first.'); } const llm = llmClient ?? new GeminiLlmClient(); const systemPrompt = 'You are an expert SaaS product manager. Given the canonical product model, produce the smallest sellable MVP plan as strict JSON.'; const plan = await llm.structuredCall({ model: 'gemini', systemPrompt, messages: [ { role: 'user', content: [ 'Canonical product model JSON:', '```json', JSON.stringify(canonical, null, 2), '```', 'Respond ONLY with JSON that matches the required schema.', ].join('\n'), }, ], schema: MvpPlanSchema, temperature: 0.2, }); await persistPhaseArtifacts(projectId, (phaseData, phaseScores, phaseHistory) => { phaseData.mvpPlan = plan; phaseScores.mvp = { overallCompletion: clamp(plan.coreFeatures.length ? 0.8 : 0.5), overallConfidence: plan.overallConfidence, updatedAt: nowIso(), }; phaseHistory.push({ phase: 'mvp', status: 'completed', timestamp: nowIso() }); return { phaseData, phaseScores, phaseHistory, nextPhase: 'mvp_ready' }; }); return plan; }