@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.
- package/dist/flows/types.js +5 -5
- package/dist/flows/types.js.map +1 -1
- package/dist/gates/estrutura.d.ts +10 -2
- package/dist/gates/estrutura.d.ts.map +1 -1
- package/dist/gates/estrutura.js +27 -4
- package/dist/gates/estrutura.js.map +1 -1
- package/dist/gates/validator.d.ts.map +1 -1
- package/dist/gates/validator.js +9 -1
- package/dist/gates/validator.js.map +1 -1
- package/dist/handlers/prototype-phase-handler.d.ts +34 -0
- package/dist/handlers/prototype-phase-handler.d.ts.map +1 -0
- package/dist/handlers/prototype-phase-handler.js +1355 -0
- package/dist/handlers/prototype-phase-handler.js.map +1 -0
- package/dist/handlers/specialist-phase-handler.js +30 -30
- package/dist/tools/consolidated/avancar.d.ts.map +1 -1
- package/dist/tools/consolidated/avancar.js +20 -0
- package/dist/tools/consolidated/avancar.js.map +1 -1
- package/dist/tools/proximo.d.ts.map +1 -1
- package/dist/tools/proximo.js +74 -9
- package/dist/tools/proximo.js.map +1 -1
- package/package.json +1 -1
|
@@ -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
|