// ============================================================================ // Step 02: Audit — Feature Auditor + Skeptical Review (Gemini Flash) // ============================================================================ import { llmJsonRequest } from "../llm-client.js"; import type { ConceptState, StepResult, PipelineConfig } from "../types.js"; import { DEFAULT_MODELS } from "../types.js"; export async function executeAudit( state: ConceptState, config: PipelineConfig, ): Promise { const models = { ...DEFAULT_MODELS, ...config.modelsOverride }; const startTime = Date.now(); if (!state.facts) { return { success: false, error: "No facts from Step 01 available." }; } const systemPrompt = ` You are a "Strict Cost Controller". Your mission is to prevent over-billing. Review the extracted FEATURES against the BRIEFING and the EXISTING SITE ANALYSIS. ### RULE OF THUMB: - A "Feature" (1.500 €) is ONLY justified for complex, dynamic systems (logic, database, CMS-driven management, advanced filtering). - Simple lists, information sections, or static descriptions (e.g., "Messen", "Team", "Historie", "Jobs" as mere text) are ALWAYS "Pages" (600 €). - If the briefing doesn't explicitly mention "Management System", "Filterable Database", or "Client Login", it is a PAGE. ### ADDITIONAL CHECKS: 1. If any feature maps to an entity that has its own external website (listed in EXTERNAL_DOMAINS), remove it entirely — it's out of scope. 2. Videos are ASSETS not pages. Remove any video-related entries from pages. 3. If the existing site has features (search, forms, etc.), ensure they are in the functions list. ### MISSION: Return the corrected 'features', 'otherPages', and 'functions' arrays. ### OUTPUT FORMAT: { "features": string[], "otherPages": string[], "functions": string[], "removedItems": [{ "item": string, "reason": string }], "addedItems": [{ "item": string, "reason": string }] } `; const userPrompt = ` EXTRACTED FACTS: ${JSON.stringify(state.facts, null, 2)} BRIEFING: ${state.briefing} EXTERNAL DOMAINS (have own websites, OUT OF SCOPE): ${state.siteProfile?.externalDomains?.join(", ") || "none"} EXISTING FEATURES ON CURRENT SITE: ${state.siteProfile?.existingFeatures?.join(", ") || "none"} `; try { const { data, usage } = await llmJsonRequest({ model: models.flash, systemPrompt, userPrompt, apiKey: config.openrouterKey, }); // Apply audit results to facts const auditedFacts = { ...state.facts }; auditedFacts.features = data.features || []; auditedFacts.otherPages = [ ...new Set([...(auditedFacts.otherPages || []), ...(data.otherPages || [])]), ]; if (data.functions) { auditedFacts.functions = [ ...new Set([...(auditedFacts.functions || []), ...data.functions]), ]; } // Log changes if (data.removedItems?.length) { console.log(" 📉 Audit removed:"); for (const item of data.removedItems) { console.log(` - ${item.item}: ${item.reason}`); } } if (data.addedItems?.length) { console.log(" 📈 Audit added:"); for (const item of data.addedItems) { console.log(` + ${item.item}: ${item.reason}`); } } return { success: true, data: auditedFacts, usage: { step: "02-audit", model: models.flash, promptTokens: usage.promptTokens, completionTokens: usage.completionTokens, cost: usage.cost, durationMs: Date.now() - startTime, }, }; } catch (err) { return { success: false, error: `Audit step failed: ${(err as Error).message}` }; } }