Files
vibn-frontend/lib/ai/mvp-agent.ts

63 lines
2.1 KiB
TypeScript

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<MvpPlan> {
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<MvpPlan>({
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;
}