@maestro-ai/mcp-server 5.4.3 → 5.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1355 @@
1
+ /**
2
+ * Prototype Phase Handler (v9.0)
3
+ *
4
+ * Handler dedicado para a fase de Prototipagem com Google Stitch.
5
+ * Gerencia o ciclo completo:
6
+ * analyzing → prompts_generated → awaiting_html → validating_html → approved
7
+ *
8
+ * Fluxo:
9
+ * 1. ANALYZING: Lê Design Doc + docs anteriores, mapeia componentes/telas
10
+ * 2. PROMPTS_GENERATED: Gera prompts otimizados para Google Stitch, salva em arquivo
11
+ * 3. AWAITING_HTML: Aguarda usuário retornar com arquivos HTML do Stitch
12
+ * 4. VALIDATING_HTML: Valida arquivos HTML na pasta prototipos/
13
+ * 5. APPROVED: Protótipos validados, avança para próxima fase
14
+ */
15
+ import { formatResponse, formatError } from "../utils/response-formatter.js";
16
+ import { serializarEstado } from "../state/storage.js";
17
+ import { saveFile } from "../utils/persistence.js";
18
+ import { ContentResolverService } from "../services/content-resolver.service.js";
19
+ import { SkillLoaderService } from "../services/skill-loader.service.js";
20
+ import { existsSync, readFileSync, readdirSync, mkdirSync, statSync } from "fs";
21
+ import { join, extname } from "path";
22
+ import { detectIDE, getSkillsDir } from "../utils/ide-paths.js";
23
+ // === CONSTANTES ===
24
+ /** Pasta onde os protótipos HTML devem ser colocados pelo usuário */
25
+ const PROTOTYPE_OUTPUT_DIR = 'prototipos';
26
+ /** Pasta de prompts gerados */
27
+ const PROMPTS_OUTPUT_FILE = 'prototipos/stitch-prompts.md';
28
+ /** Documento de validação final */
29
+ const VALIDATION_OUTPUT_FILE = 'prototipos/validacao-prototipos.md';
30
+ // === HELPERS ===
31
+ function getPrototypeDir(diretorio) {
32
+ return join(diretorio, PROTOTYPE_OUTPUT_DIR);
33
+ }
34
+ function getPromptsFilePath(diretorio) {
35
+ return join(diretorio, PROMPTS_OUTPUT_FILE);
36
+ }
37
+ function getValidationFilePath(diretorio) {
38
+ return join(diretorio, VALIDATION_OUTPUT_FILE);
39
+ }
40
+ /**
41
+ * Cria a pasta prototipos/ se não existir
42
+ */
43
+ function ensurePrototypeDir(diretorio) {
44
+ const dir = getPrototypeDir(diretorio);
45
+ if (!existsSync(dir)) {
46
+ mkdirSync(dir, { recursive: true });
47
+ }
48
+ return dir;
49
+ }
50
+ /**
51
+ * Busca arquivos HTML na pasta prototipos/
52
+ */
53
+ function findHtmlFiles(diretorio) {
54
+ const dir = getPrototypeDir(diretorio);
55
+ if (!existsSync(dir))
56
+ return [];
57
+ try {
58
+ const files = readdirSync(dir, { recursive: true });
59
+ return files
60
+ .filter(f => {
61
+ const ext = extname(String(f)).toLowerCase();
62
+ return ext === '.html' || ext === '.htm';
63
+ })
64
+ .map(f => String(f));
65
+ }
66
+ catch {
67
+ return [];
68
+ }
69
+ }
70
+ /**
71
+ * Analisa um arquivo HTML e retorna métricas detalhadas
72
+ */
73
+ function analyzeHtmlFile(content) {
74
+ const lower = content.toLowerCase();
75
+ // Componentes
76
+ const divCount = (content.match(/<div/gi) || []).length;
77
+ const sectionTags = (content.match(/<(section|article|aside|header|footer|main|nav)/gi) || []).length;
78
+ const componentCount = divCount + sectionTags;
79
+ // CSS classes
80
+ const classMatches = content.match(/class="([^"]*)"/gi) || [];
81
+ const allClasses = classMatches.flatMap(m => (m.match(/class="([^"]*)"/i)?.[1] || '').split(/\s+/));
82
+ const uniqueClasses = new Set(allClasses.filter(c => c.length > 0)).size;
83
+ // Media queries (responsividade)
84
+ const mediaQueryMatches = content.match(/@media/gi) || [];
85
+ const hasMediaQueries = mediaQueryMatches.length > 0;
86
+ const mediaQueryCount = mediaQueryMatches.length;
87
+ // Forms
88
+ const formMatches = content.match(/<form/gi) || [];
89
+ const hasForms = formMatches.length > 0;
90
+ const formCount = formMatches.length;
91
+ // Estados
92
+ const hasHoverStates = /:hover/i.test(content);
93
+ const hasLoadingStates = /loading|spinner|skeleton|shimmer|pulse/i.test(content);
94
+ const hasErrorStates = /error|invalid|danger|alert-danger|text-red|text-danger/i.test(content);
95
+ const hasDisabledStates = /disabled|:disabled|opacity.*0\.[3-5]|cursor.*not-allowed/i.test(content);
96
+ // Navegação
97
+ const hasNavigation = /<nav/i.test(content) || /navbar|sidebar|menu|breadcrumb/i.test(content);
98
+ // Botões
99
+ const buttonMatches = content.match(/<button/gi) || [];
100
+ const btnClassMatches = content.match(/class="[^"]*btn[^"]*"/gi) || [];
101
+ const hasButtons = buttonMatches.length > 0 || btnClassMatches.length > 0;
102
+ const buttonCount = Math.max(buttonMatches.length, btnClassMatches.length);
103
+ // Inputs
104
+ const inputMatches = content.match(/<input/gi) || [];
105
+ const selectMatches = content.match(/<select/gi) || [];
106
+ const textareaMatches = content.match(/<textarea/gi) || [];
107
+ const hasInputs = inputMatches.length + selectMatches.length + textareaMatches.length > 0;
108
+ const inputCount = inputMatches.length + selectMatches.length + textareaMatches.length;
109
+ // Componentes UI
110
+ const hasModals = /modal|dialog|overlay|backdrop/i.test(content);
111
+ const hasTables = /<table/i.test(content);
112
+ const hasCards = /card|panel/i.test(content);
113
+ const hasIcons = /icon|svg|fa-|material-icons|lucide/i.test(content);
114
+ const hasAlerts = /alert|notification|toast|snackbar/i.test(content);
115
+ const hasBadges = /badge|chip|tag|label/i.test(content);
116
+ const hasTooltips = /tooltip|popover/i.test(content);
117
+ // Design System
118
+ const hasColorVariables = /--[a-z]+-color|--primary|--secondary|--accent|var\(--/i.test(content);
119
+ const hasFontFamily = /font-family/i.test(content);
120
+ const hasFontSizes = /font-size/i.test(content);
121
+ const hasFlexOrGrid = /display\s*:\s*(flex|grid)/i.test(content);
122
+ const hasTransitions = /transition/i.test(content);
123
+ const hasAnimations = /animation|@keyframes/i.test(content);
124
+ // Acessibilidade
125
+ const hasAriaAttributes = /aria-|role="/i.test(content);
126
+ // Contagens
127
+ const linkCount = (content.match(/<a\s/gi) || []).length;
128
+ const imageCount = (content.match(/<img/gi) || []).length;
129
+ const sectionCount = sectionTags;
130
+ // CSS size
131
+ const styleBlocks = content.match(/<style[^>]*>[\s\S]*?<\/style>/gi) || [];
132
+ const cssSize = styleBlocks.reduce((acc, block) => acc + block.length, 0);
133
+ return {
134
+ componentCount, uniqueClasses, hasMediaQueries, mediaQueryCount,
135
+ hasForms, formCount, hasHoverStates, hasLoadingStates, hasErrorStates,
136
+ hasDisabledStates, hasNavigation, hasButtons, buttonCount, hasInputs,
137
+ inputCount, hasModals, hasTables, hasCards, hasIcons, hasAlerts,
138
+ hasBadges, hasTooltips, hasColorVariables, hasFontFamily, hasFontSizes,
139
+ hasFlexOrGrid, hasTransitions, hasAnimations, hasAriaAttributes,
140
+ linkCount, imageCount, sectionCount, cssSize, totalSize: content.length,
141
+ };
142
+ }
143
+ /**
144
+ * Valida os arquivos HTML com scoring granular alinhado ao checklist (100 pontos)
145
+ *
146
+ * Categorias:
147
+ * - Componentes: 40 pts (principais 20, design system 10, estados 5, reutilização 5)
148
+ * - Fluxos: 30 pts (principais 15, navegação 5, feedback visual 5, erros 5)
149
+ * - Design: 20 pts (cores 5, tipografia 5, espaçamento 5, responsividade 5)
150
+ * - Qualidade: 10 pts (código exportado 5, feedback stakeholders 3, documentação 2)
151
+ */
152
+ function validateHtmlFiles(diretorio, htmlFiles) {
153
+ const dir = getPrototypeDir(diretorio);
154
+ const fileDetails = [];
155
+ const allAnalyses = [];
156
+ // Analisar cada arquivo
157
+ for (const file of htmlFiles) {
158
+ const fullPath = join(dir, file);
159
+ try {
160
+ const content = readFileSync(fullPath, 'utf-8');
161
+ const stat = statSync(fullPath);
162
+ const analysis = analyzeHtmlFile(content);
163
+ allAnalyses.push(analysis);
164
+ fileDetails.push({
165
+ file,
166
+ size: stat.size,
167
+ hasContent: content.trim().length > 100,
168
+ hasStructure: /<html/i.test(content) || /<div/i.test(content) || /<body/i.test(content),
169
+ hasCss: /<style/i.test(content) || /class="/i.test(content),
170
+ });
171
+ }
172
+ catch {
173
+ fileDetails.push({ file, size: 0, hasContent: false, hasStructure: false, hasCss: false });
174
+ }
175
+ }
176
+ if (allAnalyses.length === 0) {
177
+ return {
178
+ score: 0,
179
+ categories: [],
180
+ fileDetails,
181
+ summary: '❌ Nenhum arquivo HTML válido encontrado',
182
+ gaps: ['Nenhum arquivo HTML válido na pasta prototipos/'],
183
+ strengths: [],
184
+ };
185
+ }
186
+ // Agregar métricas de todos os arquivos
187
+ const totalComponents = allAnalyses.reduce((s, a) => s + a.componentCount, 0);
188
+ const totalUniqueClasses = allAnalyses.reduce((s, a) => s + a.uniqueClasses, 0);
189
+ const totalButtons = allAnalyses.reduce((s, a) => s + a.buttonCount, 0);
190
+ const totalInputs = allAnalyses.reduce((s, a) => s + a.inputCount, 0);
191
+ const totalLinks = allAnalyses.reduce((s, a) => s + a.linkCount, 0);
192
+ const totalCssSize = allAnalyses.reduce((s, a) => s + a.cssSize, 0);
193
+ const anyHasNav = allAnalyses.some(a => a.hasNavigation);
194
+ const anyHasForms = allAnalyses.some(a => a.hasForms);
195
+ const anyHasModals = allAnalyses.some(a => a.hasModals);
196
+ const anyHasTables = allAnalyses.some(a => a.hasTables);
197
+ const anyHasCards = allAnalyses.some(a => a.hasCards);
198
+ const anyHasIcons = allAnalyses.some(a => a.hasIcons);
199
+ const anyHasAlerts = allAnalyses.some(a => a.hasAlerts);
200
+ const anyHasBadges = allAnalyses.some(a => a.hasBadges);
201
+ const anyHasTooltips = allAnalyses.some(a => a.hasTooltips);
202
+ const anyHasHover = allAnalyses.some(a => a.hasHoverStates);
203
+ const anyHasLoading = allAnalyses.some(a => a.hasLoadingStates);
204
+ const anyHasError = allAnalyses.some(a => a.hasErrorStates);
205
+ const anyHasDisabled = allAnalyses.some(a => a.hasDisabledStates);
206
+ const anyHasMediaQueries = allAnalyses.some(a => a.hasMediaQueries);
207
+ const totalMediaQueries = allAnalyses.reduce((s, a) => s + a.mediaQueryCount, 0);
208
+ const anyHasColorVars = allAnalyses.some(a => a.hasColorVariables);
209
+ const anyHasFontFamily = allAnalyses.some(a => a.hasFontFamily);
210
+ const anyHasFontSizes = allAnalyses.some(a => a.hasFontSizes);
211
+ const anyHasFlexGrid = allAnalyses.some(a => a.hasFlexOrGrid);
212
+ const anyHasTransitions = allAnalyses.some(a => a.hasTransitions);
213
+ const anyHasAnimations = allAnalyses.some(a => a.hasAnimations);
214
+ const anyHasAria = allAnalyses.some(a => a.hasAriaAttributes);
215
+ // Contar tipos de componentes UI presentes
216
+ const uiComponentTypes = [anyHasNav, anyHasForms, anyHasModals, anyHasTables, anyHasCards,
217
+ anyHasIcons, anyHasAlerts, anyHasBadges, anyHasTooltips,
218
+ totalButtons > 0, totalInputs > 0].filter(Boolean).length;
219
+ // === CATEGORIA 1: COMPONENTES (40 pontos) ===
220
+ // 1.1 Componentes principais presentes (20 pts)
221
+ let componentScore = 0;
222
+ if (totalComponents >= 30 && uiComponentTypes >= 6)
223
+ componentScore = 20;
224
+ else if (totalComponents >= 20 && uiComponentTypes >= 4)
225
+ componentScore = 15;
226
+ else if (totalComponents >= 10 && uiComponentTypes >= 3)
227
+ componentScore = 10;
228
+ else if (totalComponents >= 5)
229
+ componentScore = 5;
230
+ // 1.2 Design System aderência (10 pts)
231
+ let dsScore = 0;
232
+ if (anyHasColorVars)
233
+ dsScore += 3;
234
+ if (anyHasFontFamily)
235
+ dsScore += 2;
236
+ if (anyHasFontSizes)
237
+ dsScore += 2;
238
+ if (totalUniqueClasses >= 20)
239
+ dsScore += 3;
240
+ else if (totalUniqueClasses >= 10)
241
+ dsScore += 2;
242
+ else if (totalUniqueClasses >= 5)
243
+ dsScore += 1;
244
+ dsScore = Math.min(dsScore, 10);
245
+ // 1.3 Estados implementados (5 pts)
246
+ let statesScore = 0;
247
+ const stateCount = [anyHasHover, anyHasLoading, anyHasError, anyHasDisabled].filter(Boolean).length;
248
+ if (stateCount >= 4)
249
+ statesScore = 5;
250
+ else if (stateCount >= 2)
251
+ statesScore = 3;
252
+ else if (stateCount >= 1)
253
+ statesScore = 1;
254
+ // 1.4 Reutilização (5 pts)
255
+ let reuseScore = 0;
256
+ if (totalUniqueClasses >= 30 && allAnalyses.length >= 3)
257
+ reuseScore = 5;
258
+ else if (totalUniqueClasses >= 15 && allAnalyses.length >= 2)
259
+ reuseScore = 3;
260
+ else if (totalUniqueClasses >= 5)
261
+ reuseScore = 1;
262
+ const componentsCategory = {
263
+ name: 'Componentes',
264
+ maxPoints: 40,
265
+ earnedPoints: componentScore + dsScore + statesScore + reuseScore,
266
+ items: [
267
+ { name: 'Componentes principais presentes', maxPoints: 20, earnedPoints: componentScore, status: componentScore >= 15 ? '✅' : componentScore >= 10 ? '⚠️' : '❌' },
268
+ { name: 'Design System aderência', maxPoints: 10, earnedPoints: dsScore, status: dsScore >= 7 ? '✅' : dsScore >= 5 ? '⚠️' : '❌' },
269
+ { name: 'Estados implementados', maxPoints: 5, earnedPoints: statesScore, status: statesScore >= 3 ? '✅' : statesScore >= 1 ? '⚠️' : '❌' },
270
+ { name: 'Reutilização', maxPoints: 5, earnedPoints: reuseScore, status: reuseScore >= 3 ? '✅' : reuseScore >= 1 ? '⚠️' : '❌' },
271
+ ],
272
+ };
273
+ // === CATEGORIA 2: FLUXOS (30 pontos) ===
274
+ // 2.1 Fluxos principais (15 pts)
275
+ let flowScore = 0;
276
+ const hasMultiplePages = allAnalyses.length >= 3;
277
+ const hasInteractivity = totalButtons > 0 && totalLinks > 0;
278
+ if (hasMultiplePages && hasInteractivity && anyHasForms)
279
+ flowScore = 15;
280
+ else if (hasMultiplePages && hasInteractivity)
281
+ flowScore = 10;
282
+ else if (allAnalyses.length >= 2 && (totalButtons > 0 || totalLinks > 0))
283
+ flowScore = 7;
284
+ else if (allAnalyses.length >= 1)
285
+ flowScore = 3;
286
+ // 2.2 Navegação (5 pts)
287
+ let navScore = 0;
288
+ if (anyHasNav && totalLinks >= 5)
289
+ navScore = 5;
290
+ else if (anyHasNav || totalLinks >= 3)
291
+ navScore = 3;
292
+ else if (totalLinks >= 1)
293
+ navScore = 1;
294
+ // 2.3 Feedback visual (5 pts)
295
+ let feedbackScore = 0;
296
+ const feedbackElements = [anyHasLoading, anyHasAlerts, anyHasTransitions, anyHasAnimations].filter(Boolean).length;
297
+ if (feedbackElements >= 3)
298
+ feedbackScore = 5;
299
+ else if (feedbackElements >= 2)
300
+ feedbackScore = 3;
301
+ else if (feedbackElements >= 1)
302
+ feedbackScore = 1;
303
+ // 2.4 Tratamento de erros (5 pts)
304
+ let errorScore = 0;
305
+ if (anyHasError && anyHasForms && anyHasAlerts)
306
+ errorScore = 5;
307
+ else if (anyHasError && (anyHasForms || anyHasAlerts))
308
+ errorScore = 3;
309
+ else if (anyHasError)
310
+ errorScore = 1;
311
+ const flowsCategory = {
312
+ name: 'Fluxos',
313
+ maxPoints: 30,
314
+ earnedPoints: flowScore + navScore + feedbackScore + errorScore,
315
+ items: [
316
+ { name: 'Fluxos principais funcionam', maxPoints: 15, earnedPoints: flowScore, status: flowScore >= 10 ? '✅' : flowScore >= 7 ? '⚠️' : '❌' },
317
+ { name: 'Navegação intuitiva', maxPoints: 5, earnedPoints: navScore, status: navScore >= 3 ? '✅' : navScore >= 1 ? '⚠️' : '❌' },
318
+ { name: 'Feedback visual', maxPoints: 5, earnedPoints: feedbackScore, status: feedbackScore >= 3 ? '✅' : feedbackScore >= 1 ? '⚠️' : '❌' },
319
+ { name: 'Tratamento de erros', maxPoints: 5, earnedPoints: errorScore, status: errorScore >= 3 ? '✅' : errorScore >= 1 ? '⚠️' : '❌' },
320
+ ],
321
+ };
322
+ // === CATEGORIA 3: DESIGN (20 pontos) ===
323
+ // 3.1 Cores (5 pts)
324
+ let colorScore = 0;
325
+ if (anyHasColorVars && totalCssSize > 500)
326
+ colorScore = 5;
327
+ else if (totalCssSize > 300)
328
+ colorScore = 3;
329
+ else if (totalCssSize > 100)
330
+ colorScore = 1;
331
+ // 3.2 Tipografia (5 pts)
332
+ let typoScore = 0;
333
+ if (anyHasFontFamily && anyHasFontSizes)
334
+ typoScore = 5;
335
+ else if (anyHasFontFamily || anyHasFontSizes)
336
+ typoScore = 3;
337
+ else if (totalCssSize > 200)
338
+ typoScore = 1;
339
+ // 3.3 Espaçamento (5 pts)
340
+ let spacingScore = 0;
341
+ if (anyHasFlexGrid && totalUniqueClasses >= 15)
342
+ spacingScore = 5;
343
+ else if (anyHasFlexGrid || totalUniqueClasses >= 10)
344
+ spacingScore = 3;
345
+ else if (totalCssSize > 100)
346
+ spacingScore = 1;
347
+ // 3.4 Responsividade (5 pts)
348
+ let responsiveScore = 0;
349
+ if (totalMediaQueries >= 3)
350
+ responsiveScore = 5;
351
+ else if (totalMediaQueries >= 2)
352
+ responsiveScore = 3;
353
+ else if (anyHasMediaQueries)
354
+ responsiveScore = 1;
355
+ const designCategory = {
356
+ name: 'Design',
357
+ maxPoints: 20,
358
+ earnedPoints: colorScore + typoScore + spacingScore + responsiveScore,
359
+ items: [
360
+ { name: 'Cores do Design System', maxPoints: 5, earnedPoints: colorScore, status: colorScore >= 3 ? '✅' : colorScore >= 1 ? '⚠️' : '❌' },
361
+ { name: 'Tipografia consistente', maxPoints: 5, earnedPoints: typoScore, status: typoScore >= 3 ? '✅' : typoScore >= 1 ? '⚠️' : '❌' },
362
+ { name: 'Espaçamento uniforme', maxPoints: 5, earnedPoints: spacingScore, status: spacingScore >= 3 ? '✅' : spacingScore >= 1 ? '⚠️' : '❌' },
363
+ { name: 'Responsividade', maxPoints: 5, earnedPoints: responsiveScore, status: responsiveScore >= 3 ? '✅' : responsiveScore >= 1 ? '⚠️' : '❌' },
364
+ ],
365
+ };
366
+ // === CATEGORIA 4: QUALIDADE (10 pontos) ===
367
+ // 4.1 Código exportado (5 pts)
368
+ let codeScore = 0;
369
+ const allHaveContent = fileDetails.every(f => f.hasContent && f.hasStructure);
370
+ const allHaveCss = fileDetails.every(f => f.hasCss);
371
+ if (allHaveContent && allHaveCss && allAnalyses.length >= 3)
372
+ codeScore = 5;
373
+ else if (allHaveContent && allHaveCss)
374
+ codeScore = 3;
375
+ else if (allHaveContent)
376
+ codeScore = 1;
377
+ // 4.2 Feedback stakeholders (3 pts) — verificar se existe documento de feedback
378
+ let feedbackStakeholderScore = 0;
379
+ const feedbackDocPath = join(diretorio, 'prototipos', 'feedback-stakeholders.md');
380
+ const validationDocPath = getValidationFilePath(diretorio);
381
+ if (existsSync(feedbackDocPath))
382
+ feedbackStakeholderScore = 3;
383
+ else if (existsSync(validationDocPath))
384
+ feedbackStakeholderScore = 1;
385
+ // 4.3 Documentação (2 pts) — verificar se existe prototipos.md
386
+ let docScore = 0;
387
+ const protoDocPath = join(diretorio, 'prototipos', 'prototipos.md');
388
+ const promptsPath = getPromptsFilePath(diretorio);
389
+ if (existsSync(protoDocPath) && existsSync(promptsPath))
390
+ docScore = 2;
391
+ else if (existsSync(protoDocPath) || existsSync(promptsPath))
392
+ docScore = 1;
393
+ const qualityCategory = {
394
+ name: 'Qualidade',
395
+ maxPoints: 10,
396
+ earnedPoints: codeScore + feedbackStakeholderScore + docScore,
397
+ items: [
398
+ { name: 'Código exportado disponível', maxPoints: 5, earnedPoints: codeScore, status: codeScore >= 3 ? '✅' : codeScore >= 1 ? '⚠️' : '❌' },
399
+ { name: 'Feedback stakeholders', maxPoints: 3, earnedPoints: feedbackStakeholderScore, status: feedbackStakeholderScore >= 2 ? '✅' : feedbackStakeholderScore >= 1 ? '⚠️' : '❌' },
400
+ { name: 'Documentação completa', maxPoints: 2, earnedPoints: docScore, status: docScore >= 1 ? '✅' : '❌' },
401
+ ],
402
+ };
403
+ // === SCORE TOTAL ===
404
+ const categories = [componentsCategory, flowsCategory, designCategory, qualityCategory];
405
+ const totalScore = categories.reduce((s, c) => s + c.earnedPoints, 0);
406
+ // Identificar gaps e strengths
407
+ const gaps = [];
408
+ const strengths = [];
409
+ for (const cat of categories) {
410
+ for (const item of cat.items) {
411
+ if (item.status === '❌')
412
+ gaps.push(`${item.name} (${item.earnedPoints}/${item.maxPoints})`);
413
+ else if (item.status === '⚠️')
414
+ gaps.push(`${item.name} — pode melhorar (${item.earnedPoints}/${item.maxPoints})`);
415
+ else
416
+ strengths.push(`${item.name} (${item.earnedPoints}/${item.maxPoints})`);
417
+ }
418
+ }
419
+ // Gerar summary
420
+ const fileSummary = fileDetails.map(d => `${d.hasContent && d.hasStructure ? '✅' : d.hasContent ? '⚠️' : '❌'} **${d.file}** — ${formatSize(d.size)}${d.hasCss ? '' : ' (sem CSS)'}${d.hasStructure ? '' : ' (sem estrutura HTML)'}`).join('\n');
421
+ const categorySummary = categories.map(c => `### ${c.name} (${c.earnedPoints}/${c.maxPoints})\n${c.items.map(i => `${i.status} ${i.name}: ${i.earnedPoints}/${i.maxPoints}`).join('\n')}`).join('\n\n');
422
+ return {
423
+ score: totalScore,
424
+ categories,
425
+ fileDetails,
426
+ summary: `## 📄 Arquivos\n\n${fileSummary}\n\n## 📊 Detalhamento por Categoria\n\n${categorySummary}`,
427
+ gaps,
428
+ strengths,
429
+ };
430
+ }
431
+ function formatSize(bytes) {
432
+ if (bytes < 1024)
433
+ return `${bytes} bytes`;
434
+ if (bytes < 1024 * 1024)
435
+ return `${(bytes / 1024).toFixed(1)} KB`;
436
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
437
+ }
438
+ /**
439
+ * Tenta localizar o Design Doc da fase anterior (UX Design)
440
+ */
441
+ function findDesignDoc(diretorio, estado) {
442
+ // Tentar paths comuns do Design Doc
443
+ const candidates = [
444
+ join(diretorio, 'docs/03-ux-design/design-doc.md'),
445
+ join(diretorio, 'docs/fase-03-ux-design/design-doc.md'),
446
+ join(diretorio, 'docs/03-ux/design-doc.md'),
447
+ join(diretorio, 'docs/fase-03-ux/design-doc.md'),
448
+ ];
449
+ // Também verificar entregáveis salvos no estado
450
+ for (const key of Object.keys(estado.entregaveis || {})) {
451
+ const path = estado.entregaveis[key];
452
+ if (path && existsSync(path)) {
453
+ const content = readFileSync(path, 'utf-8');
454
+ if (content.toLowerCase().includes('design') || content.toLowerCase().includes('ux')) {
455
+ return { path, content };
456
+ }
457
+ }
458
+ }
459
+ for (const candidate of candidates) {
460
+ if (existsSync(candidate)) {
461
+ try {
462
+ return { path: candidate, content: readFileSync(candidate, 'utf-8') };
463
+ }
464
+ catch { /* ignore */ }
465
+ }
466
+ }
467
+ // Buscar qualquer .md em docs/ que contenha "design"
468
+ try {
469
+ const docsDir = join(diretorio, 'docs');
470
+ if (existsSync(docsDir)) {
471
+ const walkDir = (dir) => {
472
+ const results = [];
473
+ try {
474
+ const entries = readdirSync(dir, { withFileTypes: true });
475
+ for (const entry of entries) {
476
+ const fullPath = join(dir, entry.name);
477
+ if (entry.isDirectory()) {
478
+ results.push(...walkDir(fullPath));
479
+ }
480
+ else if (entry.name.toLowerCase().includes('design') && extname(entry.name) === '.md') {
481
+ results.push(fullPath);
482
+ }
483
+ }
484
+ }
485
+ catch { /* ignore */ }
486
+ return results;
487
+ };
488
+ const designFiles = walkDir(docsDir);
489
+ if (designFiles.length > 0) {
490
+ const content = readFileSync(designFiles[0], 'utf-8');
491
+ return { path: designFiles[0], content };
492
+ }
493
+ }
494
+ }
495
+ catch { /* ignore */ }
496
+ return null;
497
+ }
498
+ /**
499
+ * Resolve a IDE do projeto
500
+ */
501
+ function resolveIDE(estado, diretorio) {
502
+ return estado.ide || detectIDE(diretorio) || 'windsurf';
503
+ }
504
+ /**
505
+ * Persiste estado atualizado
506
+ */
507
+ async function persistState(estado, diretorio) {
508
+ estado.atualizado_em = new Date().toISOString();
509
+ const estadoFile = serializarEstado(estado);
510
+ try {
511
+ await saveFile(`${diretorio}/${estadoFile.path}`, estadoFile.content);
512
+ }
513
+ catch (err) {
514
+ console.error('[prototype-phase] Erro ao salvar estado:', err);
515
+ }
516
+ }
517
+ /**
518
+ * Obtém ou inicializa os dados da fase de prototipagem
519
+ */
520
+ function getPrototypeData(collectedData) {
521
+ return {
522
+ prototypeStatus: collectedData.prototypeStatus || 'analyzing',
523
+ designDocPath: collectedData.designDocPath,
524
+ designDocContent: collectedData.designDocContent,
525
+ mappedComponents: collectedData.mappedComponents || [],
526
+ mappedScreens: collectedData.mappedScreens || [],
527
+ designSystem: collectedData.designSystem,
528
+ promptsGenerated: collectedData.promptsGenerated || false,
529
+ promptsFilePath: collectedData.promptsFilePath,
530
+ htmlFiles: collectedData.htmlFiles || [],
531
+ validationScore: collectedData.validationScore,
532
+ validationDetails: collectedData.validationDetails,
533
+ iterationCount: collectedData.iterationCount || 0,
534
+ };
535
+ }
536
+ function savePrototypeData(collectedData, data) {
537
+ Object.assign(collectedData, data);
538
+ }
539
+ // === ENTRY POINT ===
540
+ /**
541
+ * Entry point do prototype phase handler.
542
+ * Detecta sub-estado e delega para o handler correto.
543
+ */
544
+ export async function handlePrototypePhase(args) {
545
+ const { estado, diretorio } = args;
546
+ const onboarding = estado.onboarding;
547
+ const sp = onboarding?.specialistPhase;
548
+ if (!sp) {
549
+ return {
550
+ content: formatError("prototype-phase", "Nenhuma fase de especialista ativa encontrada.", `Use \`maestro({diretorio: "${diretorio}"})\` para verificar o status do projeto.`),
551
+ isError: true,
552
+ };
553
+ }
554
+ const data = getPrototypeData(sp.collectedData);
555
+ // Criar pasta prototipos/ automaticamente
556
+ ensurePrototypeDir(diretorio);
557
+ switch (data.prototypeStatus) {
558
+ case 'analyzing':
559
+ return handleAnalyzing(args, sp, data);
560
+ case 'prompts_generated':
561
+ return handlePromptsGenerated(args, sp, data);
562
+ case 'awaiting_html': {
563
+ // Verificar se há HTMLs na pasta
564
+ const htmlFiles = findHtmlFiles(diretorio);
565
+ if (htmlFiles.length > 0) {
566
+ data.prototypeStatus = 'validating_html';
567
+ data.htmlFiles = htmlFiles;
568
+ savePrototypeData(sp.collectedData, data);
569
+ return handleValidatingHtml(args, sp, data);
570
+ }
571
+ return handleAwaitingHtml(args, sp, data);
572
+ }
573
+ case 'validating_html':
574
+ return handleValidatingHtml(args, sp, data);
575
+ case 'approved':
576
+ return handleApproved(args, sp, data);
577
+ default:
578
+ return {
579
+ content: formatError("prototype-phase", `Sub-estado desconhecido: ${data.prototypeStatus}`, `Use \`executar({diretorio: "${diretorio}", acao: "avancar"})\` para tentar avançar.`),
580
+ isError: true,
581
+ };
582
+ }
583
+ }
584
+ // === SUB-HANDLERS ===
585
+ /**
586
+ * Etapa 1: ANALYZING
587
+ * Lê Design Doc, mapeia componentes e telas, prepara contexto para geração de prompts
588
+ */
589
+ async function handleAnalyzing(args, sp, data) {
590
+ const { estado, diretorio, respostas } = args;
591
+ // Se recebeu respostas com informações de design
592
+ if (respostas) {
593
+ if (respostas.design_system)
594
+ data.designSystem = String(respostas.design_system);
595
+ if (respostas.telas_prioritarias) {
596
+ data.mappedScreens = Array.isArray(respostas.telas_prioritarias)
597
+ ? respostas.telas_prioritarias.map(String)
598
+ : String(respostas.telas_prioritarias).split(',').map(s => s.trim());
599
+ }
600
+ if (respostas.componentes) {
601
+ data.mappedComponents = Array.isArray(respostas.componentes)
602
+ ? respostas.componentes.map(String)
603
+ : String(respostas.componentes).split(',').map(s => s.trim());
604
+ }
605
+ }
606
+ // Tentar encontrar Design Doc
607
+ const designDoc = findDesignDoc(diretorio, estado);
608
+ if (designDoc) {
609
+ data.designDocPath = designDoc.path;
610
+ // Guardar resumo (não conteúdo completo para economizar tokens)
611
+ data.designDocContent = designDoc.content.substring(0, 3000);
612
+ }
613
+ // Se já temos informações suficientes para gerar prompts
614
+ const hasDesignDoc = !!data.designDocPath;
615
+ const hasScreens = data.mappedScreens && data.mappedScreens.length > 0;
616
+ const hasDesignSystem = !!data.designSystem;
617
+ if (hasDesignDoc && hasScreens && hasDesignSystem) {
618
+ // Avançar para geração de prompts
619
+ data.prototypeStatus = 'prompts_generated';
620
+ sp.status = 'generating';
621
+ savePrototypeData(sp.collectedData, data);
622
+ await persistState(estado, diretorio);
623
+ return handlePromptsGenerated(args, sp, data);
624
+ }
625
+ // Ainda precisa de informações
626
+ sp.status = 'collecting';
627
+ savePrototypeData(sp.collectedData, data);
628
+ await persistState(estado, diretorio);
629
+ const ide = resolveIDE(estado, diretorio);
630
+ const skillDir = `${getSkillsDir(ide)}/specialist-prototipagem-stitch`;
631
+ const missingItems = [];
632
+ if (!hasDesignDoc)
633
+ missingItems.push('Design Doc da fase anterior');
634
+ if (!hasDesignSystem)
635
+ missingItems.push('Design System escolhido (Material, Ant Design, Chakra UI, Custom)');
636
+ if (!hasScreens)
637
+ missingItems.push('3-5 telas prioritárias para prototipar');
638
+ const designDocInfo = hasDesignDoc
639
+ ? `✅ **Design Doc encontrado:** \`${data.designDocPath}\``
640
+ : `❌ **Design Doc não encontrado** — Verifique se a fase de UX Design foi concluída`;
641
+ return {
642
+ content: formatResponse({
643
+ titulo: "🎨 Prototipagem com Google Stitch — Etapa 1: Análise",
644
+ resumo: `Mapeando componentes e telas para prototipagem. ${3 - missingItems.length}/3 informações coletadas.`,
645
+ dados: {
646
+ "Design Doc": hasDesignDoc ? '✅ Encontrado' : '❌ Não encontrado',
647
+ "Design System": data.designSystem || '❌ Não definido',
648
+ "Telas mapeadas": hasScreens ? data.mappedScreens.join(', ') : '❌ Nenhuma',
649
+ },
650
+ instrucoes: `## 📋 Análise do Projeto para Prototipagem
651
+
652
+ ${designDocInfo}
653
+
654
+ ${hasDesignDoc ? `### Conteúdo do Design Doc (resumo)
655
+ Leia o arquivo completo: \`${data.designDocPath}\`
656
+ ` : ''}
657
+
658
+ ### 📚 Recursos do Especialista
659
+ - Template de Prompts: \`${skillDir}/resources/templates/prompt-stitch.md\`
660
+ - Checklist de Validação: \`${skillDir}/resources/checklists/stitch-validation.md\`
661
+ - Guia Completo: \`${skillDir}/SKILL.md\`
662
+
663
+ ---
664
+
665
+ ## ❌ Informações Faltantes
666
+
667
+ ${missingItems.map(item => `- **${item}**`).join('\n')}
668
+
669
+ ### 🎯 Pergunte ao usuário:
670
+ ${!hasDesignSystem ? '1. **Qual Design System usar?** (Material Design, Ant Design, Chakra UI, ou Custom com cores específicas)\n' : ''}${!hasScreens ? '2. **Quais são as 3-5 telas mais importantes para prototipar primeiro?** (ex: Dashboard, Login, Perfil, Listagem)\n' : ''}${!hasDesignDoc ? '3. **Onde está o Design Doc?** (ou peça para o usuário descrever o layout desejado)\n' : ''}
671
+
672
+ ⚠️ **NÃO** peça decisões de stack/infraestrutura — isso é da fase de Arquitetura.
673
+ Foco é **100% visual**: transformar o Design Doc em protótipos funcionais.
674
+
675
+ ## 📍 Onde Estamos
676
+ 🔄 Análise → ⏳ Geração de Prompts → ⏳ Prototipagem no Stitch → ⏳ Validação HTML → ⏳ Aprovação`,
677
+ proximo_passo: {
678
+ tool: "executar",
679
+ descricao: "Enviar informações de design coletadas",
680
+ args: `{ "diretorio": "${diretorio}", "acao": "avancar", "respostas": { "design_system": "<Material Design|Ant Design|Chakra UI|Custom>", "telas_prioritarias": ["<tela1>", "<tela2>", "<tela3>"] } }`,
681
+ requer_input_usuario: true,
682
+ prompt_usuario: "Informe o Design System e as telas prioritárias para prototipagem.",
683
+ },
684
+ }),
685
+ next_action: {
686
+ tool: "executar",
687
+ description: "Enviar informações de design para geração de prompts",
688
+ args_template: {
689
+ diretorio,
690
+ acao: "avancar",
691
+ respostas: {
692
+ design_system: "<Design System>",
693
+ telas_prioritarias: ["<tela1>", "<tela2>", "<tela3>"],
694
+ },
695
+ },
696
+ requires_user_input: true,
697
+ user_prompt: "Informe o Design System e as telas prioritárias.",
698
+ },
699
+ specialist_persona: {
700
+ name: "Prototipagem Rápida com Google Stitch",
701
+ tone: "Visual e iterativo",
702
+ expertise: ["Google Stitch", "prototipagem rápida", "design system integration", "prompt engineering para UI"],
703
+ instructions: "Foque em mapear componentes e telas do Design Doc. NÃO peça decisões de stack/infraestrutura.",
704
+ },
705
+ progress: {
706
+ current_phase: "prototipagem_analyzing",
707
+ total_phases: 5,
708
+ completed_phases: 0,
709
+ percentage: 10,
710
+ },
711
+ };
712
+ }
713
+ /**
714
+ * Etapa 2: PROMPTS_GENERATED
715
+ * Gera prompts otimizados para Google Stitch e salva em arquivo
716
+ */
717
+ async function handlePromptsGenerated(args, sp, data) {
718
+ const { estado, diretorio } = args;
719
+ // Carregar template de prompts da skill
720
+ let promptTemplate = "";
721
+ try {
722
+ const contentResolver = new ContentResolverService(diretorio);
723
+ const skillLoader = new SkillLoaderService(contentResolver);
724
+ const pkg = await skillLoader.loadFullPackage('specialist-prototipagem-stitch');
725
+ if (pkg?.templateContent) {
726
+ promptTemplate = pkg.templateContent;
727
+ }
728
+ }
729
+ catch (err) {
730
+ console.warn('[prototype-phase] Falha ao carregar template de prompts:', err);
731
+ }
732
+ const ide = resolveIDE(estado, diretorio);
733
+ const promptsPath = getPromptsFilePath(diretorio);
734
+ // Construir contexto para geração de prompts
735
+ const screensInfo = data.mappedScreens && data.mappedScreens.length > 0
736
+ ? data.mappedScreens.map((s, i) => `${i + 1}. **${s}**`).join('\n')
737
+ : 'Nenhuma tela mapeada';
738
+ const componentsInfo = data.mappedComponents && data.mappedComponents.length > 0
739
+ ? data.mappedComponents.map((c, i) => `${i + 1}. ${c}`).join('\n')
740
+ : 'Nenhum componente mapeado';
741
+ sp.status = 'generating';
742
+ data.prototypeStatus = 'prompts_generated';
743
+ savePrototypeData(sp.collectedData, data);
744
+ await persistState(estado, diretorio);
745
+ return {
746
+ content: formatResponse({
747
+ titulo: "📝 Prototipagem — Etapa 2: Geração de Prompts para Stitch",
748
+ resumo: "Informações coletadas. Gere os prompts otimizados para Google Stitch.",
749
+ dados: {
750
+ "Design System": data.designSystem || 'Não definido',
751
+ "Telas": String(data.mappedScreens?.length || 0),
752
+ "Componentes": String(data.mappedComponents?.length || 0),
753
+ "Design Doc": data.designDocPath ? '✅' : '❌',
754
+ },
755
+ instrucoes: `🤖 **AÇÃO AUTOMÁTICA REQUERIDA:**
756
+ Você DEVE gerar os prompts para Google Stitch e salvá-los no arquivo \`${PROMPTS_OUTPUT_FILE}\`.
757
+
758
+ ## 📋 Contexto para Geração
759
+
760
+ ### Design System: ${data.designSystem || 'A definir'}
761
+
762
+ ### Telas Prioritárias
763
+ ${screensInfo}
764
+
765
+ ### Componentes Mapeados
766
+ ${componentsInfo}
767
+
768
+ ${data.designDocPath ? `### Design Doc
769
+ Leia o arquivo completo para contexto: \`${data.designDocPath}\`\n` : ''}
770
+
771
+ ## 🎨 Template de Referência para Prompts
772
+
773
+ ${promptTemplate ? `Leia o template completo: \`${getSkillsDir(ide)}/specialist-prototipagem-stitch/resources/templates/prompt-stitch.md\`
774
+
775
+ ### Estrutura de cada prompt:
776
+ \`\`\`
777
+ Create a [Design System] [tipo de componente/tela] with:
778
+ - [Funcionalidade 1]
779
+ - [Funcionalidade 2]
780
+ - Responsive layout for [dispositivos]
781
+ - Color scheme: Primary [#HEX], Secondary [#HEX]
782
+ - Typography: [Font family]
783
+ \`\`\`` : 'Gere prompts estruturados para cada tela/componente.'}
784
+
785
+ ## ⚠️ INSTRUÇÕES DE GERAÇÃO
786
+
787
+ 1. **Gere 1 prompt por tela** listada acima
788
+ 2. Cada prompt deve incluir: Design System, cores, tipografia, componentes, responsividade
789
+ 3. **SALVE todos os prompts** no arquivo \`${PROMPTS_OUTPUT_FILE}\`
790
+ 4. Após salvar, avance:
791
+
792
+ \`\`\`json
793
+ executar({
794
+ "diretorio": "${diretorio}",
795
+ "acao": "avancar"
796
+ })
797
+ \`\`\`
798
+
799
+ 🤖 **NÃO ESPERE** o usuário dizer "pode seguir". Gere os prompts, salve e execute a tool AGORA.
800
+
801
+ ## 📍 Onde Estamos
802
+ ✅ Análise → 🔄 Geração de Prompts → ⏳ Prototipagem no Stitch → ⏳ Validação HTML → ⏳ Aprovação`,
803
+ proximo_passo: {
804
+ tool: "executar",
805
+ descricao: "Salvar prompts e avançar para aguardar HTML",
806
+ args: `{ "diretorio": "${diretorio}", "acao": "avancar" }`,
807
+ requer_input_usuario: false,
808
+ auto_execute: true,
809
+ },
810
+ }),
811
+ next_action: {
812
+ tool: "executar",
813
+ description: "Salvar prompts em prototipos/stitch-prompts.md e avançar",
814
+ args_template: { diretorio, acao: "avancar" },
815
+ requires_user_input: false,
816
+ auto_execute: true,
817
+ },
818
+ specialist_persona: {
819
+ name: "Prototipagem Rápida com Google Stitch",
820
+ tone: "Visual e iterativo",
821
+ expertise: ["Google Stitch", "prompt engineering para UI", "design system integration"],
822
+ instructions: "Gere prompts otimizados para cada tela. Use o Design System especificado. Seja específico com cores, tipografia e responsividade.",
823
+ },
824
+ progress: {
825
+ current_phase: "prototipagem_prompts",
826
+ total_phases: 5,
827
+ completed_phases: 1,
828
+ percentage: 30,
829
+ },
830
+ };
831
+ }
832
+ /**
833
+ * Etapa 3: AWAITING_HTML
834
+ * Aguarda o usuário retornar com arquivos HTML do Google Stitch
835
+ */
836
+ async function handleAwaitingHtml(args, sp, data) {
837
+ const { estado, diretorio } = args;
838
+ // Verificar se prompts foram salvos
839
+ const promptsExist = existsSync(getPromptsFilePath(diretorio));
840
+ if (!promptsExist && !data.promptsGenerated) {
841
+ // Prompts ainda não foram gerados — voltar para etapa anterior
842
+ data.prototypeStatus = 'prompts_generated';
843
+ savePrototypeData(sp.collectedData, data);
844
+ await persistState(estado, diretorio);
845
+ return handlePromptsGenerated(args, sp, data);
846
+ }
847
+ // Marcar prompts como gerados
848
+ data.promptsGenerated = true;
849
+ data.promptsFilePath = getPromptsFilePath(diretorio);
850
+ data.prototypeStatus = 'awaiting_html';
851
+ sp.status = 'collecting';
852
+ savePrototypeData(sp.collectedData, data);
853
+ await persistState(estado, diretorio);
854
+ const protoDir = getPrototypeDir(diretorio);
855
+ const htmlFiles = findHtmlFiles(diretorio);
856
+ return {
857
+ content: formatResponse({
858
+ titulo: "🌐 Prototipagem — Etapa 3: Prototipagem no Google Stitch",
859
+ resumo: `Prompts gerados! Agora o usuário deve usar os prompts no stitch.withgoogle.com e retornar os arquivos HTML.`,
860
+ dados: {
861
+ "Prompts salvos": promptsExist ? `✅ ${PROMPTS_OUTPUT_FILE}` : '❌ Não encontrado',
862
+ "Pasta de protótipos": `${PROTOTYPE_OUTPUT_DIR}/`,
863
+ "Arquivos HTML encontrados": String(htmlFiles.length),
864
+ },
865
+ instrucoes: `## 🎯 Instruções para o Usuário
866
+
867
+ ### Passo 1: Acesse o Google Stitch
868
+ Abra **[stitch.withgoogle.com](https://stitch.withgoogle.com)** no navegador.
869
+
870
+ ### Passo 2: Use os Prompts Gerados
871
+ Os prompts estão salvos em: \`${PROMPTS_OUTPUT_FILE}\`
872
+
873
+ Para cada tela/componente:
874
+ 1. Copie o prompt correspondente do arquivo
875
+ 2. Cole no Google Stitch
876
+ 3. Itere até obter o resultado desejado
877
+ 4. **Exporte o código HTML** do Stitch
878
+
879
+ ### Passo 3: Salve os Arquivos HTML
880
+ Coloque os arquivos HTML exportados na pasta:
881
+
882
+ 📁 **\`${protoDir}\`**
883
+
884
+ Estrutura esperada:
885
+ \`\`\`
886
+ ${PROTOTYPE_OUTPUT_DIR}/
887
+ ├── dashboard.html
888
+ ├── login.html
889
+ ├── perfil.html
890
+ ├── listagem.html
891
+ └── ... (outros protótipos)
892
+ \`\`\`
893
+
894
+ ### Passo 4: Avise quando terminar
895
+ Após colocar todos os arquivos HTML na pasta, diga **"protótipos prontos"** ou execute:
896
+
897
+ \`\`\`json
898
+ executar({
899
+ "diretorio": "${diretorio}",
900
+ "acao": "avancar"
901
+ })
902
+ \`\`\`
903
+
904
+ ---
905
+
906
+ ⚠️ **IMPORTANTE:**
907
+ - A fase **só será concluída** quando os arquivos HTML estiverem na pasta \`${PROTOTYPE_OUTPUT_DIR}/\`
908
+ - O sistema validará automaticamente os arquivos HTML
909
+ - Você pode iterar quantas vezes quiser antes de avançar
910
+
911
+ ${htmlFiles.length > 0 ? `\n### 📄 Arquivos HTML já encontrados:\n${htmlFiles.map(f => `- \`${f}\``).join('\n')}\n\n> Para validar estes arquivos, execute \`executar({acao: "avancar"})\`` : ''}
912
+
913
+ ## 📍 Onde Estamos
914
+ ✅ Análise → ✅ Geração de Prompts → 🔄 Prototipagem no Stitch → ⏳ Validação HTML → ⏳ Aprovação`,
915
+ proximo_passo: {
916
+ tool: "executar",
917
+ descricao: "Verificar arquivos HTML na pasta prototipos/",
918
+ args: `{ "diretorio": "${diretorio}", "acao": "avancar" }`,
919
+ requer_input_usuario: true,
920
+ prompt_usuario: "Coloque os arquivos HTML exportados do Stitch na pasta prototipos/ e diga 'protótipos prontos'.",
921
+ },
922
+ }),
923
+ next_action: {
924
+ tool: "executar",
925
+ description: "Verificar arquivos HTML após usuário colocar na pasta",
926
+ args_template: { diretorio, acao: "avancar" },
927
+ requires_user_input: true,
928
+ user_prompt: "Coloque os arquivos HTML na pasta prototipos/ e avise quando terminar.",
929
+ },
930
+ specialist_persona: {
931
+ name: "Prototipagem Rápida com Google Stitch",
932
+ tone: "Visual e iterativo",
933
+ expertise: ["Google Stitch", "prototipagem rápida", "export HTML/CSS"],
934
+ instructions: "Aguarde o usuário retornar com os arquivos HTML. Oriente sobre como usar o Stitch se necessário.",
935
+ },
936
+ progress: {
937
+ current_phase: "prototipagem_awaiting_html",
938
+ total_phases: 5,
939
+ completed_phases: 2,
940
+ percentage: 50,
941
+ },
942
+ };
943
+ }
944
+ /** Thresholds alinhados ao checklist stitch-validation.md */
945
+ const SCORE_AUTO_APPROVE = 75; // >= 75: aprovação automática (Bom/Excelente)
946
+ const SCORE_MANUAL_APPROVE = 60; // 60-74: aprovação manual necessária (Aceitável)
947
+ // < 60: bloqueado (Insuficiente)
948
+ /**
949
+ * Gera instruções de correção específicas baseadas nos gaps identificados
950
+ */
951
+ function generateCorrectionInstructions(validation, diretorio) {
952
+ const ide = 'windsurf'; // fallback
953
+ const skillDir = `.windsurf/skills/specialist-prototipagem-stitch`;
954
+ const gapInstructions = [];
955
+ for (const cat of validation.categories) {
956
+ const lowItems = cat.items.filter(i => i.status === '❌' || i.status === '⚠️');
957
+ if (lowItems.length === 0)
958
+ continue;
959
+ gapInstructions.push(`### ${cat.name} (${cat.earnedPoints}/${cat.maxPoints})`);
960
+ for (const item of lowItems) {
961
+ const missing = item.maxPoints - item.earnedPoints;
962
+ gapInstructions.push(`${item.status} **${item.name}**: ${item.earnedPoints}/${item.maxPoints} — faltam **${missing} pontos**`);
963
+ // Instruções específicas por item
964
+ if (item.name.includes('Componentes principais')) {
965
+ gapInstructions.push(' → Adicione mais componentes HTML: `<nav>`, `<form>`, `<table>`, cards, modais, ícones');
966
+ }
967
+ else if (item.name.includes('Design System')) {
968
+ gapInstructions.push(' → Use CSS variables (`--primary`, `--secondary`), `font-family`, `font-size`, classes consistentes');
969
+ }
970
+ else if (item.name.includes('Estados')) {
971
+ gapInstructions.push(' → Adicione `:hover`, loading states, error states, `disabled` em botões/inputs');
972
+ }
973
+ else if (item.name.includes('Reutilização')) {
974
+ gapInstructions.push(' → Use classes CSS reutilizáveis em múltiplos arquivos HTML');
975
+ }
976
+ else if (item.name.includes('Fluxos principais')) {
977
+ gapInstructions.push(' → Crie 3+ páginas HTML com botões, links e formulários interconectados');
978
+ }
979
+ else if (item.name.includes('Navegação')) {
980
+ gapInstructions.push(' → Adicione `<nav>` com links entre as páginas do protótipo');
981
+ }
982
+ else if (item.name.includes('Feedback visual')) {
983
+ gapInstructions.push(' → Adicione loading indicators, alertas, transitions CSS, animações');
984
+ }
985
+ else if (item.name.includes('Tratamento de erros')) {
986
+ gapInstructions.push(' → Adicione validação de formulários, mensagens de erro, alertas de erro');
987
+ }
988
+ else if (item.name.includes('Cores')) {
989
+ gapInstructions.push(' → Use CSS variables para cores e aumente o CSS inline (`<style>`)');
990
+ }
991
+ else if (item.name.includes('Tipografia')) {
992
+ gapInstructions.push(' → Defina `font-family` e `font-size` consistentes no CSS');
993
+ }
994
+ else if (item.name.includes('Espaçamento')) {
995
+ gapInstructions.push(' → Use `display: flex` ou `display: grid` para layout estruturado');
996
+ }
997
+ else if (item.name.includes('Responsividade')) {
998
+ gapInstructions.push(' → Adicione `@media` queries para mobile, tablet e desktop (3+ breakpoints)');
999
+ }
1000
+ else if (item.name.includes('Feedback stakeholders')) {
1001
+ gapInstructions.push(` → Crie \`prototipos/feedback-stakeholders.md\` com feedback documentado`);
1002
+ }
1003
+ else if (item.name.includes('Documentação')) {
1004
+ gapInstructions.push(` → Crie \`prototipos/prototipos.md\` com resumo dos protótipos`);
1005
+ }
1006
+ }
1007
+ gapInstructions.push('');
1008
+ }
1009
+ return gapInstructions.join('\n');
1010
+ }
1011
+ /**
1012
+ * Etapa 4: VALIDATING_HTML
1013
+ * Valida os arquivos HTML encontrados na pasta prototipos/
1014
+ *
1015
+ * Thresholds (alinhados ao checklist stitch-validation.md):
1016
+ * - >= 75: Aprovação automática (Bom/Excelente)
1017
+ * - 60-74: Aprovação manual necessária (Aceitável)
1018
+ * - < 60: Bloqueado (Insuficiente)
1019
+ */
1020
+ async function handleValidatingHtml(args, sp, data) {
1021
+ const { estado, diretorio } = args;
1022
+ const htmlFiles = findHtmlFiles(diretorio);
1023
+ if (htmlFiles.length === 0) {
1024
+ // Nenhum HTML encontrado — voltar para awaiting
1025
+ data.prototypeStatus = 'awaiting_html';
1026
+ savePrototypeData(sp.collectedData, data);
1027
+ await persistState(estado, diretorio);
1028
+ return handleAwaitingHtml(args, sp, data);
1029
+ }
1030
+ // Validar arquivos com scoring granular
1031
+ const validation = validateHtmlFiles(diretorio, htmlFiles);
1032
+ data.htmlFiles = htmlFiles;
1033
+ data.validationScore = validation.score;
1034
+ data.validationDetails = validation.summary;
1035
+ data.iterationCount++;
1036
+ sp.status = 'validating';
1037
+ sp.validationScore = validation.score;
1038
+ const ide = resolveIDE(estado, diretorio);
1039
+ const skillDir = `${getSkillsDir(ide)}/specialist-prototipagem-stitch`;
1040
+ // === SCORE >= 75: APROVAÇÃO AUTOMÁTICA ===
1041
+ if (validation.score >= SCORE_AUTO_APPROVE) {
1042
+ data.prototypeStatus = 'approved';
1043
+ sp.status = 'approved';
1044
+ sp.completedAt = new Date().toISOString();
1045
+ savePrototypeData(sp.collectedData, data);
1046
+ await persistState(estado, diretorio);
1047
+ // Gerar documento de validação
1048
+ const validationDoc = generateValidationDocument(data, validation, diretorio);
1049
+ try {
1050
+ await saveFile(getValidationFilePath(diretorio), validationDoc);
1051
+ }
1052
+ catch (err) {
1053
+ console.warn('[prototype-phase] Falha ao salvar validação:', err);
1054
+ }
1055
+ return handleApproved(args, sp, data);
1056
+ }
1057
+ // === SCORE 60-74: APROVAÇÃO MANUAL NECESSÁRIA ===
1058
+ if (validation.score >= SCORE_MANUAL_APPROVE) {
1059
+ savePrototypeData(sp.collectedData, data);
1060
+ await persistState(estado, diretorio);
1061
+ const correctionInstructions = generateCorrectionInstructions(validation, diretorio);
1062
+ const classification = validation.score >= 70 ? 'Aceitável ⚠️' : 'Aceitável (baixo) ⚠️';
1063
+ return {
1064
+ content: formatResponse({
1065
+ titulo: "⚠️ Validação dos Protótipos — Aprovação Manual Necessária",
1066
+ resumo: `Score: ${validation.score}/100 (${classification}). ${htmlFiles.length} arquivo(s). Iteração ${data.iterationCount}. Mínimo para auto-aprovação: ${SCORE_AUTO_APPROVE}.`,
1067
+ dados: {
1068
+ "Arquivos HTML": String(htmlFiles.length),
1069
+ "Score": `${validation.score}/100`,
1070
+ "Classificação": classification,
1071
+ "Auto-aprovação": `>= ${SCORE_AUTO_APPROVE}`,
1072
+ "Iteração": String(data.iterationCount),
1073
+ },
1074
+ instrucoes: `${validation.summary}
1075
+
1076
+ ## � Gaps Identificados
1077
+
1078
+ ${correctionInstructions}
1079
+
1080
+ ## ✅ Pontos Fortes
1081
+ ${validation.strengths.length > 0 ? validation.strengths.map(s => `- ✅ ${s}`).join('\n') : '- Nenhum item com pontuação máxima'}
1082
+
1083
+ ## 📚 Recursos Para Correção
1084
+ - **Template:** \`${skillDir}/resources/templates/prototipo-stitch.md\`
1085
+ - **Checklist:** \`${skillDir}/resources/checklists/stitch-validation.md\`
1086
+ - **Guia:** \`${skillDir}/SKILL.md\`
1087
+
1088
+ ## 🔄 Como Corrigir
1089
+ 1. Leia o checklist → Veja quais critérios não foram atendidos
1090
+ 2. Edite os arquivos HTML → Adicione os elementos faltantes
1091
+ 3. Re-submeta → \`executar({acao: "avancar"})\`
1092
+
1093
+ ## 🔐 Ação do Usuário Necessária
1094
+ - **Para corrigir** (recomendado): Siga as instruções acima e re-submeta
1095
+ - **Para aprovar mesmo assim**: Diga "aprovar o gate" ou "aprovar protótipos"
1096
+
1097
+ > ⚠️ A IA **NÃO** pode aprovar automaticamente. Aguarde a decisão do usuário.
1098
+
1099
+ ## 📍 Onde Estamos
1100
+ ✅ Análise → ✅ Geração de Prompts → ✅ Prototipagem no Stitch → 🔄 Validação HTML → ⏳ Aprovação`,
1101
+ proximo_passo: {
1102
+ tool: "executar",
1103
+ descricao: "Re-validar após melhorias ou aguardar aprovação manual",
1104
+ args: `{ "diretorio": "${diretorio}", "acao": "avancar" }`,
1105
+ requer_input_usuario: true,
1106
+ prompt_usuario: "Corrija os gaps ou diga 'aprovar o gate' para avançar.",
1107
+ },
1108
+ }),
1109
+ next_action: {
1110
+ tool: "executar",
1111
+ description: "Re-validar após correções ou aguardar aprovação manual",
1112
+ args_template: { diretorio, acao: "avancar" },
1113
+ requires_user_input: true,
1114
+ user_prompt: "Corrija os gaps ou diga 'aprovar o gate' para avançar.",
1115
+ },
1116
+ progress: {
1117
+ current_phase: "prototipagem_validating",
1118
+ total_phases: 5,
1119
+ completed_phases: 3,
1120
+ percentage: 70,
1121
+ },
1122
+ };
1123
+ }
1124
+ // === SCORE < 60: BLOQUEADO ===
1125
+ savePrototypeData(sp.collectedData, data);
1126
+ await persistState(estado, diretorio);
1127
+ const correctionInstructions = generateCorrectionInstructions(validation, diretorio);
1128
+ return {
1129
+ content: formatResponse({
1130
+ titulo: "❌ Validação dos Protótipos — Bloqueado",
1131
+ resumo: `Score: ${validation.score}/100 (Insuficiente). Mínimo: ${SCORE_MANUAL_APPROVE}. ${htmlFiles.length} arquivo(s). Iteração ${data.iterationCount}.`,
1132
+ dados: {
1133
+ "Arquivos HTML": String(htmlFiles.length),
1134
+ "Score": `${validation.score}/100`,
1135
+ "Classificação": "Insuficiente ❌",
1136
+ "Mínimo para aprovação manual": String(SCORE_MANUAL_APPROVE),
1137
+ "Mínimo para auto-aprovação": String(SCORE_AUTO_APPROVE),
1138
+ "Iteração": String(data.iterationCount),
1139
+ },
1140
+ instrucoes: `${validation.summary}
1141
+
1142
+ ## ❌ Gaps Críticos — Correção Obrigatória
1143
+
1144
+ ${correctionInstructions}
1145
+
1146
+ ## 📚 Recursos Para Correção
1147
+ - **Template:** \`${skillDir}/resources/templates/prototipo-stitch.md\`
1148
+ - **Checklist:** \`${skillDir}/resources/checklists/stitch-validation.md\`
1149
+ - **Guia:** \`${skillDir}/SKILL.md\`
1150
+
1151
+ ## 🔄 Como Corrigir
1152
+ 1. **Leia o checklist** → Veja EXATAMENTE quais critérios não foram atendidos
1153
+ 2. **Edite os arquivos HTML** na pasta \`${PROTOTYPE_OUTPUT_DIR}/\`
1154
+ 3. **Adicione os elementos faltantes** listados acima
1155
+ 4. **Re-submeta**: \`executar({acao: "avancar"})\`
1156
+
1157
+ > ⛔ **NÃO é possível aprovar manualmente** com score < ${SCORE_MANUAL_APPROVE}. Corrija os itens acima primeiro.
1158
+
1159
+ ## 📍 Onde Estamos
1160
+ ✅ Análise → ✅ Geração de Prompts → ✅ Prototipagem no Stitch → 🔄 Validação HTML → ⏳ Aprovação`,
1161
+ proximo_passo: {
1162
+ tool: "executar",
1163
+ descricao: "Re-validar após correções nos arquivos HTML",
1164
+ args: `{ "diretorio": "${diretorio}", "acao": "avancar" }`,
1165
+ requer_input_usuario: true,
1166
+ prompt_usuario: "Corrija os arquivos HTML conforme instruções e avise quando prontos.",
1167
+ },
1168
+ }),
1169
+ next_action: {
1170
+ tool: "executar",
1171
+ description: "Re-validar arquivos HTML após correções obrigatórias",
1172
+ args_template: { diretorio, acao: "avancar" },
1173
+ requires_user_input: true,
1174
+ user_prompt: "Corrija os arquivos HTML e avise quando prontos.",
1175
+ },
1176
+ progress: {
1177
+ current_phase: "prototipagem_validating",
1178
+ total_phases: 5,
1179
+ completed_phases: 3,
1180
+ percentage: 70,
1181
+ },
1182
+ };
1183
+ }
1184
+ /**
1185
+ * Etapa 5: APPROVED
1186
+ * Protótipos validados, preparar transição para próxima fase
1187
+ */
1188
+ async function handleApproved(args, sp, data) {
1189
+ const { estado, diretorio } = args;
1190
+ const onboarding = estado.onboarding;
1191
+ const htmlFiles = data.htmlFiles || findHtmlFiles(diretorio);
1192
+ const protoDir = getPrototypeDir(diretorio);
1193
+ // Limpar specialistPhase para que avancar.ts não redirecione de volta
1194
+ const finalScore = data.validationScore || 0;
1195
+ const finalIteration = data.iterationCount;
1196
+ // Marcar fase como concluída no onboarding
1197
+ if (onboarding) {
1198
+ delete onboarding.specialistPhase;
1199
+ onboarding.phase = 'completed';
1200
+ onboarding.completedAt = new Date().toISOString();
1201
+ }
1202
+ // NÃO avançar fase_atual aqui — isso é responsabilidade do proximo.ts
1203
+ // Apenas marcar que o entregável está pronto
1204
+ estado.status = 'ativo';
1205
+ await persistState(estado, diretorio);
1206
+ // Gerar entregável prototipos.md com resumo
1207
+ const entregavelContent = generatePrototypeSummary(data, htmlFiles, diretorio);
1208
+ const entregavelPath = join(protoDir, 'prototipos.md');
1209
+ try {
1210
+ await saveFile(entregavelPath, entregavelContent);
1211
+ }
1212
+ catch (err) {
1213
+ console.warn('[prototype-phase] Falha ao salvar entregável:', err);
1214
+ }
1215
+ return {
1216
+ content: formatResponse({
1217
+ titulo: "✅ Protótipos Aprovados!",
1218
+ resumo: `${htmlFiles.length} protótipo(s) HTML validado(s) com score ${finalScore}/100. Fase de prototipagem concluída!`,
1219
+ dados: {
1220
+ "Arquivos HTML": String(htmlFiles.length),
1221
+ "Score Final": `${finalScore}/100`,
1222
+ "Iterações": String(finalIteration),
1223
+ "Pasta": `${PROTOTYPE_OUTPUT_DIR}/`,
1224
+ "Entregável": `${PROTOTYPE_OUTPUT_DIR}/prototipos.md`,
1225
+ },
1226
+ instrucoes: `## 🎉 Prototipagem Concluída!
1227
+
1228
+ ### 📁 Arquivos do Protótipo
1229
+ ${htmlFiles.map(f => `- \`${PROTOTYPE_OUTPUT_DIR}/${f}\``).join('\n')}
1230
+
1231
+ ### 📄 Documentos Gerados
1232
+ - Prompts: \`${PROMPTS_OUTPUT_FILE}\`
1233
+ - Validação: \`${VALIDATION_OUTPUT_FILE}\`
1234
+ - Resumo: \`${PROTOTYPE_OUTPUT_DIR}/prototipos.md\`
1235
+
1236
+ ### 🚀 Próximos Passos
1237
+ 1. Os protótipos HTML servem como referência visual para o desenvolvimento
1238
+ 2. O código exportado pode ser usado como base para componentes frontend
1239
+ 3. O Design Doc foi validado visualmente através dos protótipos
1240
+
1241
+ Para avançar para a próxima fase (Arquitetura):
1242
+
1243
+ \`\`\`json
1244
+ executar({
1245
+ "diretorio": "${diretorio}",
1246
+ "acao": "avancar"
1247
+ })
1248
+ \`\`\`
1249
+
1250
+ ## 📍 Onde Estamos
1251
+ ✅ Análise → ✅ Geração de Prompts → ✅ Prototipagem no Stitch → ✅ Validação HTML → ✅ Aprovação`,
1252
+ proximo_passo: {
1253
+ tool: "executar",
1254
+ descricao: "Avançar para a próxima fase",
1255
+ args: `{ "diretorio": "${diretorio}", "acao": "avancar" }`,
1256
+ requer_input_usuario: false,
1257
+ auto_execute: true,
1258
+ },
1259
+ }),
1260
+ next_action: {
1261
+ tool: "executar",
1262
+ description: "Avançar para próxima fase (Arquitetura)",
1263
+ args_template: { diretorio, acao: "avancar" },
1264
+ requires_user_input: false,
1265
+ auto_execute: true,
1266
+ },
1267
+ specialist_persona: {
1268
+ name: "Prototipagem Rápida com Google Stitch",
1269
+ tone: "Visual e iterativo",
1270
+ expertise: ["Google Stitch", "prototipagem rápida", "export HTML/CSS"],
1271
+ instructions: "Protótipos aprovados. Prepare a transição para a fase de Arquitetura.",
1272
+ },
1273
+ progress: {
1274
+ current_phase: "prototipagem_approved",
1275
+ total_phases: 5,
1276
+ completed_phases: 5,
1277
+ percentage: 100,
1278
+ },
1279
+ };
1280
+ }
1281
+ // === DOCUMENT GENERATORS ===
1282
+ function generateValidationDocument(data, validation, diretorio) {
1283
+ const now = new Date().toISOString().split('T')[0];
1284
+ return `# Validação de Protótipos — ${now}
1285
+
1286
+ ## Resumo
1287
+ - **Score:** ${validation.score}/100
1288
+ - **Arquivos:** ${validation.fileDetails.length}
1289
+ - **Iterações:** ${data.iterationCount}
1290
+ - **Design System:** ${data.designSystem || 'N/A'}
1291
+
1292
+ ## Arquivos Validados
1293
+
1294
+ | Arquivo | Tamanho | Conteúdo | Estrutura HTML |
1295
+ |---------|---------|----------|----------------|
1296
+ ${validation.fileDetails.map((d) => `| ${d.file} | ${formatSize(d.size)} | ${d.hasContent ? '✅' : '❌'} | ${d.hasStructure ? '✅' : '❌'} |`).join('\n')}
1297
+
1298
+ ## Telas Prototipadas
1299
+ ${(data.mappedScreens || []).map((s, i) => `${i + 1}. ${s}`).join('\n') || 'N/A'}
1300
+
1301
+ ## Componentes Mapeados
1302
+ ${(data.mappedComponents || []).map((c, i) => `${i + 1}. ${c}`).join('\n') || 'N/A'}
1303
+
1304
+ ---
1305
+ *Gerado automaticamente pelo Maestro — Fase de Prototipagem*
1306
+ `;
1307
+ }
1308
+ function generatePrototypeSummary(data, htmlFiles, diretorio) {
1309
+ const now = new Date().toISOString().split('T')[0];
1310
+ return `# Protótipos — Resumo da Fase
1311
+
1312
+ **Data:** ${now}
1313
+ **Design System:** ${data.designSystem || 'N/A'}
1314
+ **Score de Validação:** ${data.validationScore || 0}/100
1315
+
1316
+ ## Arquivos HTML
1317
+
1318
+ ${htmlFiles.map(f => `- \`${f}\``).join('\n') || 'Nenhum arquivo'}
1319
+
1320
+ ## Telas Prototipadas
1321
+
1322
+ ${(data.mappedScreens || []).map((s, i) => `${i + 1}. **${s}**`).join('\n') || 'N/A'}
1323
+
1324
+ ## Componentes
1325
+
1326
+ ${(data.mappedComponents || []).map((c, i) => `${i + 1}. ${c}`).join('\n') || 'N/A'}
1327
+
1328
+ ## Design Doc de Referência
1329
+
1330
+ ${data.designDocPath ? `\`${data.designDocPath}\`` : 'Não encontrado'}
1331
+
1332
+ ## Prompts Utilizados
1333
+
1334
+ Arquivo: \`${PROMPTS_OUTPUT_FILE}\`
1335
+
1336
+ ---
1337
+
1338
+ ### Próximos Passos
1339
+ 1. Usar protótipos como referência visual no desenvolvimento frontend
1340
+ 2. Componentes HTML podem ser adaptados para o framework escolhido
1341
+ 3. Manter protótipos atualizados conforme mudanças de design
1342
+
1343
+ ---
1344
+ *Gerado automaticamente pelo Maestro — Fase de Prototipagem*
1345
+ `;
1346
+ }
1347
+ /**
1348
+ * Verifica se a fase atual é de prototipagem (Stitch) pelo nome da fase
1349
+ */
1350
+ export function isPrototypePhase(faseNome, usarStitch) {
1351
+ if (!usarStitch)
1352
+ return false;
1353
+ return faseNome?.toLowerCase() === 'prototipagem';
1354
+ }
1355
+ //# sourceMappingURL=prototype-phase-handler.js.map