Phase 6: train a second LoRA for real Penpot UI design capability #5
@@ -0,0 +1,77 @@
|
||||
[OK] los 8 prompts son disjuntos de train/eval/seeds (7472 shingles de referencia)
|
||||
[MCP] handshake ok con 'penpot-mcp-server'; tools: execute_code, high_level_overview, penpot_api_info, export_shape
|
||||
[MCP] sonda de conectividad del plugin: {
|
||||
"result": {
|
||||
"gate5Probe": true
|
||||
},
|
||||
"log": ""
|
||||
}
|
||||
|
||||
--- g5-01-boton-primario (baja) -> pagina gate5/prod-baseline/g5-01-boton-primario/20260730T175220Z
|
||||
[MCP] fallo transitorio en 'tools/call' (intento 1/5): error de transporte contra el MCP de Penpot: ConnectionError. Reintento en 4s
|
||||
[MCP] rehaciendo el handshake (la sesion pudo haber muerto)
|
||||
[MCP] handshake ok con 'penpot-mcp-server'; tools: execute_code, high_level_overview, penpot_api_info, export_shape
|
||||
[WARN] g5-01-boton-primario: se agotaron los 14 turnos sin mensaje final
|
||||
[WARN] g5-01-boton-primario: export_shape no devolvio binario
|
||||
score=0.0 (2/14) grises=0 prohibido=7 shapes=0 colores=0 textos=0
|
||||
|
||||
--- g5-02-navbar-flex (baja) -> pagina gate5/prod-baseline/g5-02-navbar-flex/20260730T175644Z
|
||||
[WARN] g5-02-navbar-flex: se agotaron los 14 turnos sin mensaje final
|
||||
[WARN] g5-02-navbar-flex: export_shape no devolvio binario
|
||||
score=17.6 (3/17) grises=0 prohibido=0 shapes=0 colores=0 textos=0
|
||||
|
||||
--- g5-03-card-producto (media) -> pagina gate5/prod-baseline/g5-03-card-producto/20260730T175753Z
|
||||
[WARN] g5-03-card-producto: se agotaron los 14 turnos sin mensaje final
|
||||
[WARN] g5-03-card-producto: export_shape no devolvio binario
|
||||
score=20.0 (3/15) grises=0 prohibido=0 shapes=0 colores=0 textos=0
|
||||
|
||||
--- g5-04-tokens-biblioteca (media) -> pagina gate5/prod-baseline/g5-04-tokens-biblioteca/20260730T175855Z
|
||||
[WARN] g5-04-tokens-biblioteca: se agotaron los 14 turnos sin mensaje final
|
||||
[WARN] g5-04-tokens-biblioteca: export_shape no devolvio binario
|
||||
score=20.0 (3/15) grises=0 prohibido=0 shapes=0 colores=0 textos=0
|
||||
|
||||
--- g5-05-grid-tres-diferenciales (media) -> pagina gate5/prod-baseline/g5-05-grid-tres-diferenciales/20260730T175957Z
|
||||
[WARN] g5-05-grid-tres-diferenciales: se agotaron los 14 turnos sin mensaje final
|
||||
[WARN] g5-05-grid-tres-diferenciales: export_shape no devolvio binario
|
||||
score=17.6 (3/17) grises=0 prohibido=0 shapes=0 colores=0 textos=0
|
||||
|
||||
--- g5-06-landing-pizzeria (alta) -> pagina gate5/prod-baseline/g5-06-landing-pizzeria/20260730T180102Z
|
||||
[MCP] fallo transitorio en 'tools/call' (intento 1/5): error de transporte contra el MCP de Penpot: ConnectionError. Reintento en 4s
|
||||
[MCP] rehaciendo el handshake (la sesion pudo haber muerto)
|
||||
[MCP] handshake ok con 'penpot-mcp-server'; tools: execute_code, high_level_overview, penpot_api_info, export_shape
|
||||
[WARN] g5-06-landing-pizzeria: export_shape no devolvio binario
|
||||
score=0.0 (3/17) grises=0 prohibido=5 shapes=0 colores=0 textos=0
|
||||
|
||||
--- g5-07-onboarding-mobile (alta) -> pagina gate5/prod-baseline/g5-07-onboarding-mobile/20260730T180525Z
|
||||
[WARN] g5-07-onboarding-mobile: se agotaron los 14 turnos sin mensaje final
|
||||
[WARN] g5-07-onboarding-mobile: export_shape no devolvio binario
|
||||
score=17.6 (3/17) grises=0 prohibido=0 shapes=0 colores=0 textos=0
|
||||
|
||||
--- g5-08-reparacion-grises (alta) -> pagina gate5/prod-baseline/g5-08-reparacion-grises/20260730T180627Z
|
||||
[WARN] g5-08-reparacion-grises: se agotaron los 14 turnos sin mensaje final
|
||||
[WARN] g5-08-reparacion-grises: export_shape no devolvio binario
|
||||
score=26.7 (4/15) grises=0 prohibido=0 shapes=0 colores=0 textos=0
|
||||
|
||||
=== Puerta 5 -- calidad de diseno en Penpot (tag=prod-baseline) ===
|
||||
|
||||
prompt dif score pasa grises prohib shapes colores textos
|
||||
g5-01-boton-primario baja 0.0 2/14 0 7 0 0 0
|
||||
g5-02-navbar-flex baja 17.6 3/17 0 0 0 0 0
|
||||
g5-03-card-producto media 20.0 3/15 0 0 0 0 0
|
||||
g5-04-tokens-biblioteca media 20.0 3/15 0 0 0 0 0
|
||||
g5-05-grid-tres-diferenciales media 17.6 3/17 0 0 0 0 0
|
||||
g5-06-landing-pizzeria alta 0.0 3/17 0 5 0 0 0
|
||||
g5-07-onboarding-mobile alta 17.6 3/17 0 0 0 0 0
|
||||
g5-08-reparacion-grises alta 26.7 4/15 0 0 0 0 0
|
||||
|
||||
condiciones de aprobacion:
|
||||
[NO ] score medio >= 65 -> 14.9
|
||||
[NO ] veto limpio en 8/8 -> 6/8
|
||||
[NO ] >= 6/8 prompts >= 60 -> 0/8
|
||||
[NO ] landing de pizzeria >= 60 -> 0.0
|
||||
|
||||
VEREDICTO GLOBAL: NO APRUEBA
|
||||
|
||||
tiempo total: 15.1 min
|
||||
resultados en data/gate5_results_prod-baseline.json
|
||||
PNGs en data/gate5_png_prod-baseline/ (la puerta no borra nada: las paginas quedan para inspeccion humana)
|
||||
@@ -1,4 +1,5 @@
|
||||
[OK] los 8 prompts son disjuntos de train/eval/seeds (7472 shingles de referencia)
|
||||
[SYSTEM] documento completo del servidor: 16293 chars (247 lineas)
|
||||
[OK] los 10 prompts son disjuntos de train/eval/seeds (7472 shingles de referencia)
|
||||
[MCP] handshake ok con 'penpot-mcp-server'; tools: execute_code, high_level_overview, penpot_api_info, export_shape
|
||||
[MCP] sonda de conectividad del plugin: {
|
||||
"result": {
|
||||
@@ -7,71 +8,48 @@
|
||||
"log": ""
|
||||
}
|
||||
|
||||
--- g5-01-boton-primario (baja) -> pagina gate5/prod-baseline/g5-01-boton-primario/20260730T175220Z
|
||||
[MCP] fallo transitorio en 'tools/call' (intento 1/5): error de transporte contra el MCP de Penpot: ConnectionError. Reintento en 4s
|
||||
[MCP] rehaciendo el handshake (la sesion pudo haber muerto)
|
||||
[MCP] handshake ok con 'penpot-mcp-server'; tools: execute_code, high_level_overview, penpot_api_info, export_shape
|
||||
[WARN] g5-01-boton-primario: se agotaron los 14 turnos sin mensaje final
|
||||
--- g5-01-boton-primario (baja) -> pagina gate5/prod-baseline/g5-01-boton-primario/20260730T182503Z
|
||||
[WARN] g5-01-boton-primario: export_shape no devolvio binario
|
||||
score=0.0 (2/14) grises=0 prohibido=7 shapes=0 colores=0 textos=0
|
||||
score=0.0 (2/14) grises=0 prohibido=5 shapes=0 colores=0 textos=0
|
||||
|
||||
--- g5-02-navbar-flex (baja) -> pagina gate5/prod-baseline/g5-02-navbar-flex/20260730T175644Z
|
||||
--- g5-02-navbar-flex (baja) -> pagina gate5/prod-baseline/g5-02-navbar-flex/20260730T182608Z
|
||||
[WARN] g5-02-navbar-flex: se agotaron los 14 turnos sin mensaje final
|
||||
[WARN] g5-02-navbar-flex: export_shape no devolvio binario
|
||||
score=17.6 (3/17) grises=0 prohibido=0 shapes=0 colores=0 textos=0
|
||||
score=0.0 (3/17) grises=0 prohibido=1 shapes=0 colores=0 textos=0
|
||||
|
||||
--- g5-03-card-producto (media) -> pagina gate5/prod-baseline/g5-03-card-producto/20260730T175753Z
|
||||
--- g5-03-card-producto (media) -> pagina gate5/prod-baseline/g5-03-card-producto/20260730T182706Z
|
||||
[WARN] g5-03-card-producto: se agotaron los 14 turnos sin mensaje final
|
||||
[WARN] g5-03-card-producto: export_shape no devolvio binario
|
||||
score=20.0 (3/15) grises=0 prohibido=0 shapes=0 colores=0 textos=0
|
||||
|
||||
--- g5-04-tokens-biblioteca (media) -> pagina gate5/prod-baseline/g5-04-tokens-biblioteca/20260730T175855Z
|
||||
[WARN] g5-04-tokens-biblioteca: se agotaron los 14 turnos sin mensaje final
|
||||
--- g5-04-tokens-biblioteca (media) -> pagina gate5/prod-baseline/g5-04-tokens-biblioteca/20260730T182804Z
|
||||
[WARN] g5-04-tokens-biblioteca: export_shape no devolvio binario
|
||||
score=20.0 (3/15) grises=0 prohibido=0 shapes=0 colores=0 textos=0
|
||||
score=0.0 (3/15) grises=0 prohibido=1 shapes=0 colores=0 textos=0
|
||||
|
||||
--- g5-05-grid-tres-diferenciales (media) -> pagina gate5/prod-baseline/g5-05-grid-tres-diferenciales/20260730T175957Z
|
||||
[WARN] g5-05-grid-tres-diferenciales: se agotaron los 14 turnos sin mensaje final
|
||||
--- g5-05-grid-tres-diferenciales (media) -> pagina gate5/prod-baseline/g5-05-grid-tres-diferenciales/20260730T182903Z
|
||||
[WARN] g5-05-grid-tres-diferenciales: export_shape no devolvio binario
|
||||
score=17.6 (3/17) grises=0 prohibido=0 shapes=0 colores=0 textos=0
|
||||
score=23.5 (4/17) grises=0 prohibido=0 shapes=0 colores=0 textos=0
|
||||
|
||||
--- g5-06-landing-pizzeria (alta) -> pagina gate5/prod-baseline/g5-06-landing-pizzeria/20260730T180102Z
|
||||
--- g5-06-landing-pizzeria (alta) -> pagina gate5/prod-baseline/g5-06-landing-pizzeria/20260730T183005Z
|
||||
[MCP] fallo transitorio en 'tools/call' (intento 1/5): error de transporte contra el MCP de Penpot: ConnectionError. Reintento en 4s
|
||||
[MCP] rehaciendo el handshake (la sesion pudo haber muerto)
|
||||
[MCP] handshake ok con 'penpot-mcp-server'; tools: execute_code, high_level_overview, penpot_api_info, export_shape
|
||||
[WARN] g5-06-landing-pizzeria: la auditoria fallo (g5-06-landing-pizzeria: auditoria: el MCP no devolvio JSON (texto): Tool execution failed: Error: Task 374cec0b-2e20-40bc-b58e-44e7ae20b6b0 timed out after 30 seconds)
|
||||
[WARN] g5-06-landing-pizzeria: export_shape no devolvio binario
|
||||
score=0.0 (3/17) grises=0 prohibido=5 shapes=0 colores=0 textos=0
|
||||
score=0.0 (2/21) grises=0 prohibido=1 shapes=0 colores=0 textos=0
|
||||
|
||||
--- g5-07-onboarding-mobile (alta) -> pagina gate5/prod-baseline/g5-07-onboarding-mobile/20260730T180525Z
|
||||
[WARN] g5-07-onboarding-mobile: se agotaron los 14 turnos sin mensaje final
|
||||
[WARN] g5-07-onboarding-mobile: export_shape no devolvio binario
|
||||
score=17.6 (3/17) grises=0 prohibido=0 shapes=0 colores=0 textos=0
|
||||
--- g5-07-onboarding-mobile (alta) -> pagina gate5/prod-baseline/g5-07-onboarding-mobile/20260730T183529Z
|
||||
[ERROR] g5-07-onboarding-mobile: g5-07-onboarding-mobile: setup de la pagina: el MCP no devolvio JSON (texto): Tool execution failed: Error: Task 98dda7fa-baed-4e2e-99f4-fa8bf33c1326 timed out after 30 seconds
|
||||
[INFO] se guardan los 6 prompt(s) ya medidos y se sigue con el siguiente; el JSON queda marcado como parcial
|
||||
|
||||
--- g5-08-reparacion-grises (alta) -> pagina gate5/prod-baseline/g5-08-reparacion-grises/20260730T180627Z
|
||||
[WARN] g5-08-reparacion-grises: se agotaron los 14 turnos sin mensaje final
|
||||
[WARN] g5-08-reparacion-grises: export_shape no devolvio binario
|
||||
score=26.7 (4/15) grises=0 prohibido=0 shapes=0 colores=0 textos=0
|
||||
--- g5-08-reparacion-grises (alta) -> pagina gate5/prod-baseline/g5-08-reparacion-grises/20260730T183559Z
|
||||
[ERROR] g5-08-reparacion-grises: g5-08-reparacion-grises: setup de la pagina: el MCP no devolvio JSON (texto): Tool execution failed: Error: Task 725a0fd0-3c6e-4b4b-bf1c-bb65776c4fb0 timed out after 30 seconds
|
||||
[INFO] se guardan los 7 prompt(s) ya medidos y se sigue con el siguiente; el JSON queda marcado como parcial
|
||||
|
||||
=== Puerta 5 -- calidad de diseno en Penpot (tag=prod-baseline) ===
|
||||
--- g5-09-brief-ambiguo-escuela (alta) -> pagina gate5/prod-baseline/g5-09-brief-ambiguo-escuela/20260730T183629Z
|
||||
|
||||
prompt dif score pasa grises prohib shapes colores textos
|
||||
g5-01-boton-primario baja 0.0 2/14 0 7 0 0 0
|
||||
g5-02-navbar-flex baja 17.6 3/17 0 0 0 0 0
|
||||
g5-03-card-producto media 20.0 3/15 0 0 0 0 0
|
||||
g5-04-tokens-biblioteca media 20.0 3/15 0 0 0 0 0
|
||||
g5-05-grid-tres-diferenciales media 17.6 3/17 0 0 0 0 0
|
||||
g5-06-landing-pizzeria alta 0.0 3/17 0 5 0 0 0
|
||||
g5-07-onboarding-mobile alta 17.6 3/17 0 0 0 0 0
|
||||
g5-08-reparacion-grises alta 26.7 4/15 0 0 0 0 0
|
||||
|
||||
condiciones de aprobacion:
|
||||
[NO ] score medio >= 65 -> 14.9
|
||||
[NO ] veto limpio en 8/8 -> 6/8
|
||||
[NO ] >= 6/8 prompts >= 60 -> 0/8
|
||||
[NO ] landing de pizzeria >= 60 -> 0.0
|
||||
|
||||
VEREDICTO GLOBAL: NO APRUEBA
|
||||
|
||||
tiempo total: 15.1 min
|
||||
resultados en data/gate5_results_prod-baseline.json
|
||||
PNGs en data/gate5_png_prod-baseline/ (la puerta no borra nada: las paginas quedan para inspeccion humana)
|
||||
[ABORTA] el servidor MCP de Penpot no tiene ningun plugin conectado.
|
||||
Abri el archivo de Penpot en el navegador, arranca el plugin del MCP y espera a
|
||||
que quede conectado; recien ahi volve a correr la puerta. Sin plugin conectado no
|
||||
tiene sentido gastar los 8 prompts.
|
||||
Mensaje del servidor: Tool execution failed: Error: No Penpot plugin instances are currently connected. Please ensure the plugin is running and connected.
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
{"id": "g5-03-card-producto", "dificultad": "media", "prompt": "Hacé una card de producto de 320 de ancho con una foto real traída de https://picsum.photos/seed/espresso/640/480, un título, una bajada de dos líneas, el precio destacado y un botón de compra. La foto tiene que ser la imagen de verdad: nada de rectángulos grises simulando la imagen."}
|
||||
{"id": "g5-04-tokens-biblioteca", "dificultad": "media", "prompt": "Creá en la biblioteca local del archivo una paleta de 6 colores nombrados por rol y una escala tipográfica de 5 pasos, y después aplicá esos tokens a una muestra visible: un encabezado, un párrafo de apoyo y tres chips que muestren los colores aplicados desde la biblioteca."}
|
||||
{"id": "g5-05-grid-tres-diferenciales", "dificultad": "media", "prompt": "Componé una sección de 3 diferenciales de 1140 de ancho resuelta con un grid de 3 columnas: cada columna con una figura geométrica propia a modo de ícono, un titular corto y dos líneas de texto de apoyo, todo con la misma alineación y respiración entre columnas."}
|
||||
{"id": "g5-06-landing-pizzeria", "dificultad": "alta", "prompt": "hazme una landing page de una pizzería napolitana con colores vibrantes, de 1200 de ancho"}
|
||||
{"id": "g5-06-landing-pizzeria", "dificultad": "alta", "prompt": "hazme una landing page de una pizzería con colores vibrantes", "brief": "ambiguo"}
|
||||
{"id": "g5-07-onboarding-mobile", "dificultad": "alta", "prompt": "Diseñá una pantalla mobile de onboarding de 375x812 para una app de reservas: ilustración o figura de bienvenida arriba, titular, párrafo explicativo, indicador de tres pasos y dos acciones al pie (una primaria y una secundaria)."}
|
||||
{"id": "g5-08-reparacion-grises", "dificultad": "alta", "prompt": "Este board está hecho enteramente de rectángulos grises y no tiene una sola línea de texto. Auditalo, definí una paleta y una escala tipográfica, y rehacelo hasta que sea una pantalla de verdad, con contenido legible y jerarquía visual.", "seed_board": {"nombre": "Wireframe sin disenar", "x": 0, "y": 0, "ancho": 960, "alto": 640, "gris": "#B1B2B5", "rectangulos": [{"nombre": "header", "x": 32, "y": 32, "ancho": 896, "alto": 88}, {"nombre": "hero", "x": 32, "y": 144, "ancho": 896, "alto": 208}, {"nombre": "card-1", "x": 32, "y": 376, "ancho": 288, "alto": 160}, {"nombre": "card-2", "x": 336, "y": 376, "ancho": 288, "alto": 160}, {"nombre": "card-3", "x": 640, "y": 376, "ancho": 288, "alto": 160}, {"nombre": "footer", "x": 32, "y": 560, "ancho": 896, "alto": 48}]}}
|
||||
{"id": "g5-09-brief-ambiguo-escuela", "dificultad": "alta", "brief": "ambiguo", "prompt": "necesito la home de una escuela de música para chicos, que sea alegre; no tengo paleta ni logo, elegí vos"}
|
||||
{"id": "g5-10-brief-ambiguo-notaria", "dificultad": "alta", "brief": "ambiguo", "prompt": "quiero el sitio de una notaría, que transmita seriedad; no te paso colores"}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -94,6 +94,8 @@ PROMPT_SCORE_MIN = 60.0
|
||||
MIN_PROMPTS_OVER_MIN = 6
|
||||
HOLDOUT_FORBIDDEN_RATE_MAX = 0.05
|
||||
|
||||
HEX_EN_TEXTO = re.compile(r"#[0-9a-fA-F]{6}\b")
|
||||
|
||||
PLUGIN_NOT_CONNECTED = "No Penpot plugin instances are currently connected"
|
||||
EXIT_PLUGIN_NOT_CONNECTED = 2
|
||||
|
||||
@@ -597,7 +599,54 @@ try {
|
||||
const tamsUnicos = [];
|
||||
for (const tam of fontSizes) { if (tamsUnicos.indexOf(tam) === -1) { tamsUnicos.push(tam); } }
|
||||
|
||||
// VIBRACION. Ni `distinctFillColors` ni `placeholderGreys` distinguen una paleta vibrante de
|
||||
// una apagada pero tecnicamente no gris: seis tonos polvorientos pasan las dos. Esto mide
|
||||
// saturacion de verdad, en HSL.
|
||||
function aHsl(hex) {
|
||||
const r = parseInt(hex.slice(1, 3), 16) / 255;
|
||||
const g = parseInt(hex.slice(3, 5), 16) / 255;
|
||||
const b = parseInt(hex.slice(5, 7), 16) / 255;
|
||||
const mx = Math.max(r, g, b), mn = Math.min(r, g, b), d = mx - mn;
|
||||
const l = (mx + mn) / 2;
|
||||
let h = 0, sat = 0;
|
||||
if (d !== 0) {
|
||||
sat = d / (1 - Math.abs(2 * l - 1));
|
||||
if (mx === r) { h = ((g - b) / d) % 6; }
|
||||
else if (mx === g) { h = (b - r) / d + 2; }
|
||||
else { h = (r - g) / d + 4; }
|
||||
h = h * 60; if (h < 0) { h += 360; }
|
||||
}
|
||||
return { h: h, s: sat * 100, l: l * 100 };
|
||||
}
|
||||
const cromaticos = [];
|
||||
const neutrales = [];
|
||||
for (const hex of distintos) {
|
||||
const c = aHsl(hex);
|
||||
// Cromatico: saturacion >= 45 y luminosidad en 15..85. La banda de luminosidad excluye los
|
||||
// casi-negros y casi-blancos, que pueden dar saturacion alta y sin embargo leerse neutros.
|
||||
if (c.s >= 45 && c.l >= 15 && c.l <= 85) { cromaticos.push({ hex: hex, h: c.h, s: c.s, l: c.l }); }
|
||||
else if (c.s <= 15) { neutrales.push(hex); }
|
||||
}
|
||||
// Estructura de paleta: una marca dominante, un acento con tono claramente distinto, y neutrales.
|
||||
let separacionDeTono = 0;
|
||||
for (let i = 0; i < cromaticos.length; i++) {
|
||||
for (let j = i + 1; j < cromaticos.length; j++) {
|
||||
let d = Math.abs(cromaticos[i].h - cromaticos[j].h);
|
||||
if (d > 180) { d = 360 - d; }
|
||||
if (d > separacionDeTono) { separacionDeTono = d; }
|
||||
}
|
||||
}
|
||||
const saturacionMedia = cromaticos.length
|
||||
? cromaticos.reduce(function (a, c) { return a + c.s; }, 0) / cromaticos.length : 0;
|
||||
|
||||
return {
|
||||
chromaticFills: cromaticos.length,
|
||||
chromaticSamples: cromaticos.slice(0, 8).map(function (c) {
|
||||
return { hex: c.hex, s: Math.round(c.s), l: Math.round(c.l) }; }),
|
||||
meanChromaticSaturation: Math.round(saturacionMedia),
|
||||
neutralFills: neutrales.length,
|
||||
hueSeparation: Math.round(separacionDeTono),
|
||||
paletteStructured: cromaticos.length >= 2 && neutrales.length >= 1 && separacionDeTono >= 30,
|
||||
shapeCount: nodes.length,
|
||||
distinctFillColors: distintos.length,
|
||||
fillColors: distintos.slice(0, 24),
|
||||
@@ -727,6 +776,10 @@ def check_disjunto(prompts):
|
||||
METRICS = [
|
||||
("shapeCount", "conteo de shapes", "min"),
|
||||
("distinctFillColors", "fillColor distintos (sin blanco/negro puros)", "min"),
|
||||
("chromaticFills", "fills cromaticos (sat HSL >= 45, L 15-85)", "min"),
|
||||
("meanChromaticSaturation", "saturacion media de los fills cromaticos", "min"),
|
||||
("paletteStructured", "paleta estructurada (marca + acento + neutrales)", "bool"),
|
||||
("finalMessageListsHex", "el mensaje final enumera los hex elegidos", "bool"),
|
||||
("placeholderGreys", "grises de placeholder [VETO]", "max"),
|
||||
("textsWithCharacters", "textos con characters no vacio", "min"),
|
||||
("typeScale", "fontSize distintos y maximo", "pair"),
|
||||
@@ -749,6 +802,27 @@ VETO_METRICS = ("placeholderGreys", "forbiddenBehavior")
|
||||
# Umbrales por prompt. `None` = la metrica NO aplica a ese prompt y no entra en el denominador
|
||||
# del score. Estan aca y no en el .jsonl a proposito: el .jsonl describe la TAREA, el umbral es
|
||||
# criterio de la puerta y se versiona junto al codigo que lo aplica.
|
||||
# ------------------------------------------------------------------------------------------
|
||||
# Umbrales de VIBRACION, solo para los prompts de brief ambiguo
|
||||
# ------------------------------------------------------------------------------------------
|
||||
# Requisito explicito del usuario: ante un brief ambiguo el modelo tiene que ELEGIR una paleta y
|
||||
# justificarla, no preguntar ni caer en defaults. Ni `distinctFillColors` ni `placeholderGreys`
|
||||
# distinguen una paleta vibrante de una apagada pero tecnicamente no gris.
|
||||
#
|
||||
# El umbral de saturacion 45 NO es una opinion: sale de medir las 325 paletas no neutrales del
|
||||
# corpus escrito a mano de esta fase. Su saturacion HSL mediana es 75, con p25 en 48 y p10 en 35.
|
||||
# Un piso de 45 queda apenas por debajo del primer cuartil -- lo pasa el 79% de esos fills -- asi
|
||||
# que es un suelo que el comportamiento objetivo despeja comodo, no una aspiracion. La banda de
|
||||
# luminosidad 15..85 excluye casi-negros y casi-blancos, que pueden dar saturacion alta y aun asi
|
||||
# leerse neutros.
|
||||
UMBRALES_VIBRACION = {
|
||||
"chromaticFills": 2,
|
||||
"meanChromaticSaturation": 45,
|
||||
"paletteStructured": True,
|
||||
"finalMessageListsHex": True,
|
||||
}
|
||||
UMBRALES_VIBRACION_NO_APLICA = {k: None for k in UMBRALES_VIBRACION}
|
||||
|
||||
THRESHOLDS = {
|
||||
"g5-01-boton-primario": {
|
||||
"shapeCount": 2, "distinctFillColors": 2, "placeholderGreys": 0,
|
||||
@@ -757,6 +831,7 @@ THRESHOLDS = {
|
||||
"structure": None, "styleRichness": 2, "rootHeightMismatch": 24,
|
||||
"exportBeforeFinal": True, "forbiddenBehavior": 0, "exceptionRate": 0.34,
|
||||
"fontIdBoundShare": 1.0, "rendersOk": True,
|
||||
**UMBRALES_VIBRACION_NO_APLICA,
|
||||
},
|
||||
"g5-02-navbar-flex": {
|
||||
"shapeCount": 6, "distinctFillColors": 3, "placeholderGreys": 0,
|
||||
@@ -765,6 +840,7 @@ THRESHOLDS = {
|
||||
"structure": (2, 1), "styleRichness": 1, "rootHeightMismatch": 24,
|
||||
"exportBeforeFinal": True, "forbiddenBehavior": 0, "exceptionRate": 0.34,
|
||||
"fontIdBoundShare": 1.0, "rendersOk": True,
|
||||
**UMBRALES_VIBRACION_NO_APLICA,
|
||||
},
|
||||
"g5-03-card-producto": {
|
||||
"shapeCount": 6, "distinctFillColors": 3, "placeholderGreys": 0,
|
||||
@@ -773,6 +849,7 @@ THRESHOLDS = {
|
||||
"structure": (2, 1), "styleRichness": 3, "rootHeightMismatch": 32,
|
||||
"exportBeforeFinal": True, "forbiddenBehavior": 0, "exceptionRate": 0.34,
|
||||
"fontIdBoundShare": 1.0, "rendersOk": True,
|
||||
**UMBRALES_VIBRACION_NO_APLICA,
|
||||
},
|
||||
"g5-04-tokens-biblioteca": {
|
||||
"shapeCount": 8, "distinctFillColors": 6, "placeholderGreys": 0,
|
||||
@@ -781,6 +858,7 @@ THRESHOLDS = {
|
||||
"structure": (2, 1), "styleRichness": 2, "rootHeightMismatch": 32,
|
||||
"exportBeforeFinal": True, "forbiddenBehavior": 0, "exceptionRate": 0.34,
|
||||
"fontIdBoundShare": 1.0, "rendersOk": True,
|
||||
**UMBRALES_VIBRACION_NO_APLICA,
|
||||
},
|
||||
"g5-05-grid-tres-diferenciales": {
|
||||
"shapeCount": 12, "distinctFillColors": 3, "placeholderGreys": 0,
|
||||
@@ -789,6 +867,28 @@ THRESHOLDS = {
|
||||
"structure": (3, 3), "styleRichness": 3, "rootHeightMismatch": 32,
|
||||
"exportBeforeFinal": True, "forbiddenBehavior": 0, "exceptionRate": 0.34,
|
||||
"fontIdBoundShare": 1.0, "rendersOk": True,
|
||||
**UMBRALES_VIBRACION_NO_APLICA,
|
||||
},
|
||||
"g5-09-brief-ambiguo-escuela": {
|
||||
"shapeCount": 30, "distinctFillColors": 6, "placeholderGreys": 0,
|
||||
"textsWithCharacters": 12, "typeScale": (3, 32), "textFillShare": 1.0,
|
||||
"layoutBoards": 3, "layoutChildSizingShare": 0.5, "containmentViolations": 0,
|
||||
"structure": (3, 4), "styleRichness": 3, "rootHeightMismatch": 48,
|
||||
"exportBeforeFinal": True, "forbiddenBehavior": 0, "exceptionRate": 0.34,
|
||||
"fontIdBoundShare": 1.0, "rendersOk": True,
|
||||
**UMBRALES_VIBRACION,
|
||||
},
|
||||
"g5-10-brief-ambiguo-notaria": {
|
||||
"shapeCount": 30, "distinctFillColors": 5, "placeholderGreys": 0,
|
||||
"textsWithCharacters": 12, "typeScale": (3, 32), "textFillShare": 1.0,
|
||||
"layoutBoards": 3, "layoutChildSizingShare": 0.5, "containmentViolations": 0,
|
||||
"structure": (3, 4), "styleRichness": 3, "rootHeightMismatch": 48,
|
||||
"exportBeforeFinal": True, "forbiddenBehavior": 0, "exceptionRate": 0.34,
|
||||
"fontIdBoundShare": 1.0, "rendersOk": True,
|
||||
# "elegante" no pide estridencia: alcanza con UN cromatico bien elegido sobre neutrales,
|
||||
# pero la paleta tiene que estar estructurada y declarada igual.
|
||||
"chromaticFills": 1, "meanChromaticSaturation": 30,
|
||||
"paletteStructured": True, "finalMessageListsHex": True,
|
||||
},
|
||||
"g5-06-landing-pizzeria": {
|
||||
"shapeCount": 25, "distinctFillColors": 5, "placeholderGreys": 0,
|
||||
@@ -797,6 +897,7 @@ THRESHOLDS = {
|
||||
"structure": (3, 5), "styleRichness": 5, "rootHeightMismatch": 48,
|
||||
"exportBeforeFinal": True, "forbiddenBehavior": 0, "exceptionRate": 0.34,
|
||||
"fontIdBoundShare": 1.0, "rendersOk": True,
|
||||
**UMBRALES_VIBRACION,
|
||||
},
|
||||
"g5-07-onboarding-mobile": {
|
||||
"shapeCount": 10, "distinctFillColors": 4, "placeholderGreys": 0,
|
||||
@@ -805,6 +906,7 @@ THRESHOLDS = {
|
||||
"structure": (3, 2), "styleRichness": 3, "rootHeightMismatch": 32,
|
||||
"exportBeforeFinal": True, "forbiddenBehavior": 0, "exceptionRate": 0.34,
|
||||
"fontIdBoundShare": 1.0, "rendersOk": True,
|
||||
**UMBRALES_VIBRACION_NO_APLICA,
|
||||
},
|
||||
"g5-08-reparacion-grises": {
|
||||
"shapeCount": 8, "distinctFillColors": 4, "placeholderGreys": 0,
|
||||
@@ -813,6 +915,7 @@ THRESHOLDS = {
|
||||
"structure": (2, 2), "styleRichness": 3, "rootHeightMismatch": 32,
|
||||
"exportBeforeFinal": True, "forbiddenBehavior": 0, "exceptionRate": 0.34,
|
||||
"fontIdBoundShare": 1.0, "rendersOk": True,
|
||||
**UMBRALES_VIBRACION_NO_APLICA,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -838,6 +941,10 @@ def build_raw_metrics(audit, runtime):
|
||||
"exportBeforeFinal": runtime["exportBeforeFinal"],
|
||||
"forbiddenBehavior": runtime["forbiddenBehavior"],
|
||||
"exceptionRate": runtime["exceptionRate"],
|
||||
"chromaticFills": audit.get("chromaticFills", 0),
|
||||
"meanChromaticSaturation": audit.get("meanChromaticSaturation", 0),
|
||||
"paletteStructured": bool(audit.get("paletteStructured", False)),
|
||||
"finalMessageListsHex": runtime["finalMessageListsHex"],
|
||||
"fontIdBoundShare": audit.get("fontIdBoundShare", 0.0),
|
||||
"rendersOk": bool(audit.get("cssLength", 0) > 0 and audit.get("markupLength", 0) > 0
|
||||
and not audit.get("renderError")),
|
||||
@@ -901,10 +1008,65 @@ def load_penpot_tools():
|
||||
|
||||
|
||||
def load_system_prompt():
|
||||
"""El bloque `instructions` verbatim del servidor MCP. Medir con otro system prompt mediria
|
||||
otra cosa: es exactamente lo que el modelo ve en produccion."""
|
||||
"""El bloque `instructions` COMPLETO del servidor MCP, verbatim.
|
||||
|
||||
Medir con otro system prompt mide otra cosa. Y esto se equivoco una vez, caro:
|
||||
|
||||
La primera corrida del baseline inyectaba solo la seccion inicial del documento (la que
|
||||
`penpot_system_prompt.md` guarda como "Bloque completo, verbatim"), que son 2290 de los
|
||||
16396 caracteres reales -- el 14%. El 86% que faltaba es justo el grounding de API:
|
||||
"Core Shape Properties and Methods", "Layout Systems", "Text Elements" y "The `penpot` and
|
||||
`penpotUtils` Objects", o sea insertChild, resize(), los layouts y penpotUtils.
|
||||
|
||||
Con ese recorte, produccion media shapes=0 en los 8 prompts: sin ninguna documentacion de
|
||||
API, el modelo se inventaba una con forma de Figma y todas las llamadas lanzaban. Pero el
|
||||
sintoma que el usuario reporto en su sesion real de Claude Code era otro y menos severo
|
||||
("se conecta y crea cajas grises"), o sea shapes>0. Esa discrepancia era la firma de que el
|
||||
harness, y no el modelo, estaba produciendo el resultado.
|
||||
|
||||
En una sesion real el servidor MCP entrega el documento entero como `instructions`, que es
|
||||
tambien lo que devuelve `high_level_overview`. Asi que la fuente correcta es la Captura 1 de
|
||||
penpot_api_docs.md, completa.
|
||||
|
||||
Nota deliberada sobre los seeds: los 34 seeds que llevan mensaje `system` cargan solo la
|
||||
seccion inicial, no las 4554 tokens del documento entero -- no entrarian en el presupuesto
|
||||
de 3300 tokens por ejemplo. Eso es aceptable porque la contramedida que los seeds tienen que
|
||||
ensenar (la regla de "don't pick your own colours" y el razonamiento que la desambigua) vive
|
||||
entera en esa seccion inicial.
|
||||
"""
|
||||
if os.environ.get("GATE5_SYSTEM_PROMPT_SECTION_ONLY", "").lower() in ("1", "true", "yes"):
|
||||
print("[SYSTEM] usando SOLO la seccion inicial del documento (modo de diagnostico)")
|
||||
return LINT.extract_system_block()
|
||||
|
||||
lineas = (SCHEMAS_DIR / "penpot_api_docs.md").read_text(encoding="utf-8").splitlines()
|
||||
try:
|
||||
inicio = next(n for n, l in enumerate(lineas) if l.strip() == "Salida verbatim:") + 3
|
||||
fin = next(n for n in range(inicio + 5, len(lineas)) if lineas[n].rstrip() == "```")
|
||||
except StopIteration:
|
||||
raise Gate5Error(
|
||||
"no se pudo extraer la Captura 1 (high_level_overview) de penpot_api_docs.md; "
|
||||
"ese bloque es el system prompt real del servidor y sin el la medicion no es fiel"
|
||||
)
|
||||
completo = "\n".join(lineas[inicio:fin])
|
||||
# La captura viene de `high_level_overview`, que cierra con un separador y la frase
|
||||
# "You have hereby read the 'Penpot High-Level Overview' and need not use a tool to read it
|
||||
# again." Eso es framing de la RESPUESTA DE LA TOOL, no parte del bloque `instructions` que
|
||||
# el servidor inyecta. Dejarlo adentro le estaria diciendo al modelo que ya leyo un documento
|
||||
# por haberlo recibido, lo que altera su decision de llamar o no a la tool.
|
||||
corte = completo.rfind("\n--\nYou have hereby read")
|
||||
if corte != -1:
|
||||
completo = completo[:corte]
|
||||
|
||||
seccion = LINT.extract_system_block()
|
||||
if not completo.startswith(seccion[:200]):
|
||||
raise Gate5Error(
|
||||
"la Captura 1 de penpot_api_docs.md no arranca como el bloque de "
|
||||
"penpot_system_prompt.md: uno de los dos archivos se desincronizo"
|
||||
)
|
||||
print(f"[SYSTEM] documento completo del servidor: {len(completo)} chars "
|
||||
f"({len(completo.splitlines())} lineas)")
|
||||
return completo
|
||||
|
||||
|
||||
def chat_completion(cfg, messages, tools):
|
||||
payload = {
|
||||
@@ -1073,10 +1235,14 @@ def run_prompt(cfg, mcp, tools, system_prompt, fila, png_dir):
|
||||
except Gate5Error as e:
|
||||
print(f"[WARN] {prompt_id}: no se pudo exportar el PNG ({e})")
|
||||
|
||||
# Ante un brief ambiguo el modelo tiene que ELEGIR una paleta y DECIRSELA al usuario, para
|
||||
# que la pueda ajustar. Una paleta elegida en silencio no es colaborable.
|
||||
hex_en_final = set(HEX_EN_TEXTO.findall(final_content or ""))
|
||||
runtime = {
|
||||
"exportBeforeFinal": bool(export_calls),
|
||||
"forbiddenBehavior": len(veto_hits),
|
||||
"exceptionRate": (exec_exceptions / exec_calls) if exec_calls else 0.0,
|
||||
"finalMessageListsHex": len(hex_en_final) >= 3,
|
||||
}
|
||||
raw = build_raw_metrics(audit, runtime)
|
||||
resultado = score_prompt(prompt_id, raw)
|
||||
|
||||
Reference in New Issue
Block a user