sdd-es 2.5.0 → 2.6.0

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.
Files changed (60) hide show
  1. package/.claude/settings.json +24 -0
  2. package/.claude/settings.local.json +10 -0
  3. package/.claude-plugin/marketplace.json +34 -0
  4. package/.claude-plugin/plugin.json +119 -0
  5. package/.gitignore +20 -0
  6. package/.mcp.json +8 -0
  7. package/README.md +27 -20
  8. package/agents/architecture-designer.md +37 -0
  9. package/agents/desarrollador-frontend.md +8 -15
  10. package/agents/product-designer.md +36 -0
  11. package/claude-hooks/agent-memory.js +137 -3
  12. package/claude-hooks/pre-tool-guard.js +61 -9
  13. package/commands/sdd.adr.md +196 -0
  14. package/commands/sdd.ayuda.md +13 -0
  15. package/commands/sdd.compliance.md +5 -0
  16. package/commands/sdd.configurar.md +1 -1
  17. package/commands/sdd.crear-mcp.md +2 -0
  18. package/commands/sdd.defect-report.md +134 -0
  19. package/commands/sdd.descubrir.md +19 -0
  20. package/commands/sdd.estado.md +52 -2
  21. package/commands/sdd.implementar.md +71 -31
  22. package/commands/sdd.md +23 -3
  23. package/commands/sdd.optimizar-memoria.md +47 -0
  24. package/commands/sdd.retro.md +74 -0
  25. package/commands/sdd.verificar.md +71 -0
  26. package/configuracion-ejemplo/.claude/CLAUDE.md +106 -0
  27. package/configuracion-ejemplo/sdd.config.yaml +10 -0
  28. package/docs/CASO-COMPLETO.md +206 -0
  29. package/docs/EJEMPLOS.md +88 -0
  30. package/docs/FABRICA.md +5 -6
  31. package/docs/INICIO-RAPIDO.md +27 -79
  32. package/docs/MEMORIA-Y-OBSERVABILIDAD.md +12 -10
  33. package/docs/README.md +43 -0
  34. package/docs/RELACION-CON-CLAUDE-CODE.md +38 -0
  35. package/package.json +11 -8
  36. package/plantillas/job-story-mejorar-prompt.md +107 -0
  37. package/presets/enterprise.yaml +6 -0
  38. package/presets/lean.yaml +4 -0
  39. package/presets/startup.yaml +6 -0
  40. package/skills/adr-indexer/SKILL.md +181 -0
  41. package/skills/cache-audit/SKILL.md +1 -1
  42. package/skills/critica-diseno/SKILL.md +1 -1
  43. package/skills/descubrir-idea/SKILL.md +1 -1
  44. package/skills/effort-router/SKILL.md +1 -1
  45. package/skills/elegir-direccion/SKILL.md +1 -1
  46. package/skills/interpretar-idea/SKILL.md +1 -1
  47. package/skills/mejorar-prompt/SKILL.md +237 -0
  48. package/skills/memory-compactor/SKILL.md +34 -80
  49. package/skills/mutation-detector/SKILL.md +134 -0
  50. package/skills/observabilidad-consumo/SKILL.md +1 -1
  51. package/skills/token-budget/SKILL.md +24 -1
  52. package/skills/wireframe-mvp/SKILL.md +1 -1
  53. package/mcp-figma/README.md +0 -158
  54. package/mcp-figma/package.json +0 -7
  55. package/mcp-figma/src/component-generator.js +0 -162
  56. package/mcp-figma/src/design-system-analyzer.js +0 -247
  57. package/mcp-figma/src/figma-client.js +0 -75
  58. package/mcp-figma/src/index.js +0 -114
  59. package/mcp-figma/src/mcp.js +0 -97
  60. package/mcp-figma/src/style-mapper.js +0 -85
@@ -1,114 +1,68 @@
1
1
  ---
2
- description: Comprime los archivos .sdd/memoria/agente-*.md cuando superan 80 entradas o 50KB. Elimina duplicados (misma ruta, fecha distinta — conserva la más reciente) y aplica el diccionario caveman de compresion-tokens (Nivel Full). Produce backup .original antes de comprimir.
3
- model: claude-haiku-4-5-20251001
2
+ name: memory-compactor
3
+ model: claude-haiku-4-5
4
+ description: Comprime archivos de memoria de agentes cuando superan umbral (>50KB)
4
5
  allowed-tools: Read, Write, Bash
5
6
  ---
6
7
 
7
8
  # Skill: Memory Compactor
8
9
 
9
- ## Propósito
10
-
11
- Los archivos de memoria de agentes crecen indefinidamente a lo largo de proyectos largos. Una memoria de 200 entradas consume ~30KB de ventana de contexto en cada invocación del agente — contexto que podría usarse para la tarea real. Esta skill comprime esos archivos sin perder las decisiones clave.
12
-
13
- **Cuándo ejecutar:**
14
- - Cuando el hook `agent-memory.js` emite: `⚠️ Memoria de {agente} supera 50KB`
15
- - Cuando `/sdd.optimizar memoria` detecta archivos por encima del umbral
16
- - Manualmente si el agente empieza a ignorar instrucciones (señal de contexto saturado)
10
+ **Propósito:** Comprimir automáticamente archivos de memoria de agentes (`.sdd/memoria/agente-*.md`) cuando superan 50KB, eliminando entradas duplicadas y aplicando técnicas de reducción de contexto.
17
11
 
18
12
  ---
19
13
 
20
- ## PASO 1 — Detectar archivos que superan el umbral
21
-
22
- ```bash
23
- # Listar archivos de memoria con su tamaño
24
- ls -la .sdd/memoria/agente-*.md 2>/dev/null || echo "SIN_ARCHIVOS_MEMORIA"
25
-
26
- # Contar entradas por archivo (cada entrada empieza con "## ")
27
- for f in .sdd/memoria/agente-*.md; do
28
- [ -f "$f" ] || continue
29
- entradas=$(grep -c "^## " "$f" 2>/dev/null || echo 0)
30
- bytes=$(wc -c < "$f" 2>/dev/null || echo 0)
31
- echo "$f: $entradas entradas, $bytes bytes"
32
- done
33
- ```
14
+ ## Cuándo Usar
34
15
 
35
- **Umbral de intervención:** >80 entradas O >50KB. Si ningún archivo supera el umbral, reportar "✅ Memorias dentro del umbral — no se requiere compresión" y terminar.
16
+ 1. **Automático:** Hook `agent-memory.js` la dispara cuando memoria > 50KB
17
+ 2. **Manual:** Usuario ejecuta `/sdd.optimizar memoria` en proyectos largos
36
18
 
37
19
  ---
38
20
 
39
- ## PASO 2 — Para cada archivo que supera el umbral
21
+ ## Qué Hace
40
22
 
41
- ### 2a. Crear backup
42
-
43
- ```bash
44
- cp .sdd/memoria/agente-{nombre}.md .sdd/memoria/agente-{nombre}.md.original
45
- ```
46
-
47
- ### 2b. Leer el archivo completo
48
-
49
- Parsear línea a línea. Cada entrada tiene este formato:
50
- ```markdown
51
- ## 2026-06-13 — ruta/del/archivo.md
52
- > resumen de la acción tomada
53
- ```
23
+ ### Deduplicación
24
+ Elimina entradas duplicadas del mismo archivo.
54
25
 
55
- ### 2c. Eliminar duplicados
26
+ ### Compresión por Caveman (Nivel Full)
27
+ Aplica diccionario de reemplazos: `CREATE TABLE` → `CT`, `SELECT * FROM` → `SF`, etc.
56
28
 
57
- Agrupar entradas por `archivo` (la parte después del `—` en el encabezado `##`). De cada grupo, conservar **solo la entrada más reciente** (la de fecha más alta). Las fechas están en formato ISO 8601 `YYYY-MM-DD` — ordenar lexicográficamente es suficiente.
29
+ ### Backup Automático
30
+ Crea `.original.md` antes de comprimir.
58
31
 
59
- **Ejemplo:**
60
- ```
61
- ## 2026-06-01 — .sdd/especificaciones/auth/spec.md ← ELIMINAR (más antigua)
62
- ## 2026-06-10 — .sdd/especificaciones/auth/spec.md ← CONSERVAR (más reciente)
63
- ```
64
-
65
- ### 2d. Aplicar compresión Nivel Full al cuerpo de cada entrada
66
-
67
- El diccionario completo vive en `{PLUGIN_DIR}/skills/compresion-tokens/SKILL.md`. No duplicar los pares aquí — leer la sección "Diccionario Nivel Full" de esa skill y aplicar los mismos 80+ reemplazos sobre el texto de cada línea `>`.
32
+ ---
68
33
 
69
- **Patrones que NUNCA comprimir** (igual que en `compresion-tokens`):
70
- - Rutas de archivo (la parte después del `—` en `##`)
71
- - Código entre backticks
72
- - Nombres de variables, funciones, clases
73
- - URLs y comandos
34
+ ## Output
74
35
 
75
- ### 2e. Reescribir el archivo comprimido
36
+ **Antes:** `.sdd/memoria/agente-arquitecto.md = 150KB (80 entradas)`
76
37
 
77
- ```markdown
78
- # Memoria del agente: {nombre}
38
+ **Después:** `.sdd/memoria/agente-arquitecto.md = 15KB (8 entradas únicas)`
79
39
 
80
- [Comprimido por memory-compactor el {fecha}. Backup en .original]
81
- Léelo al inicio de cada sesión para mantener continuidad entre conversaciones.
40
+ Mensaje: `✨ [auto-compress] arquitecto: 150KB 15KB (10%)`
82
41
 
83
42
  ---
84
43
 
85
- {entradas deduplicadas y comprimidas, ordenadas por fecha descendente}
86
- ```
44
+ ## Detalles Técnicos
45
+
46
+ - Deduplicación por `## YYYY-MM-DD — filepath`
47
+ - Reutiliza diccionario de `skills/compresion-tokens/SKILL.md`
48
+ - Backup `.original.md` siempre
49
+ - Performance: <100ms
50
+ - Trigger automático >50KB en `agent-memory.js`
87
51
 
88
52
  ---
89
53
 
90
- ## PASO 3 — Reporte
54
+ ## API (Internal)
91
55
 
56
+ ```javascript
57
+ triggerAutoCompresion(cwd, agente, memoriaFile)
92
58
  ```
93
- ╔══════════════════════════════════════════════════════════════╗
94
- ║ 🧠 MEMORY COMPACTOR Resultado ║
95
- ╠══════════════════════════════════════════════════════════════╣
96
- ║ AGENTE | ANTES | DESPUÉS | REDUCCIÓN ║
97
- ║ ─────────────────┼──────────────┼──────────────┼─────────── ║
98
- ║ {nombre} | {N} entr. | {M} entr. | {%}% ║
99
- ║ | {KB_antes}KB | {KB_desp}KB | ║
100
- ╠══════════════════════════════════════════════════════════════╣
101
- ║ Backups guardados en: .sdd/memoria/*.md.original ║
102
- ║ Para restaurar: cp agente-{nombre}.md.original agente-... ║
103
- ╚══════════════════════════════════════════════════════════════╝
104
- ```
59
+
60
+ Ejecuta compresión automáticamente cuando se excede umbral.
105
61
 
106
62
  ---
107
63
 
108
64
  ## Notas
109
65
 
110
- - Esta skill reutiliza el diccionario de `compresion-tokens` — no lo reinventa.
111
- - La deduplicación es la técnica más efectiva: en proyectos largos, el 40-60% de las entradas son re-escrituras del mismo archivo.
112
- - Nunca borrar el `.original` automáticamente — el usuario decide cuándo hacerlo.
113
- - Si un archivo tiene <20 entradas y <20KB, saltar silenciosamente aunque se haya invocado la skill.
114
- - Esta skill se invoca desde `/sdd.optimizar memoria` y desde `/sdd.comprimir memoria`.
66
+ - Idempotente (ejecutar 2 veces = mismo resultado)
67
+ - Nunca pierdes datos (backup existe siempre)
68
+ - No interrumpe flujos (solo stderr messages)
@@ -0,0 +1,134 @@
1
+ ---
2
+ name: mutation-detector
3
+ model: claude-haiku-4-5
4
+ description: Detecta mutaciones (cambios) en archivos para tracking de calidad
5
+ allowed-tools: Read, Bash
6
+ ---
7
+
8
+ # Skill: Mutation Detector
9
+
10
+ **Propósito:** Analizar patrones de cambio en archivos para detectar inestabilidad y calcular defect escape rate.
11
+
12
+ ---
13
+
14
+ ## Cómo Funciona
15
+
16
+ ### Ledger de Mutaciones
17
+
18
+ Cada vez que un agente modifica un archivo (Write, Edit, MultiEdit), el hook registra:
19
+ ```json
20
+ {
21
+ "ts": "2026-06-14T10:30:00Z",
22
+ "agente": "backend-dev",
23
+ "archivo": "src/auth.ts",
24
+ "tool": "Edit",
25
+ "tipo": "partial"
26
+ }
27
+ ```
28
+
29
+ **Campo `tipo`:**
30
+ - `full` — Write (archivo nuevamente escrito desde cero)
31
+ - `partial` — Edit (cambios parciales al archivo)
32
+
33
+ ### Análisis de Estabilidad
34
+
35
+ Mutation Detector agrupa por archivo:
36
+ ```
37
+ src/auth.ts:
38
+ - Mutación 1: backend-dev (full) 2026-06-10 10:00
39
+ - Mutación 2: backend-dev (partial) 2026-06-11 14:30
40
+ - Mutación 3: tester-qa (partial) 2026-06-11 15:00 (test fail)
41
+
42
+ Análisis:
43
+ - Total mutaciones: 3
44
+ - Autor principal: backend-dev
45
+ - Estabilidad: INESTABLE (3 cambios en 2 días)
46
+ - Defect finder: tester-qa encontró 1 bug
47
+ ```
48
+
49
+ ---
50
+
51
+ ## Defect Escape Rate
52
+
53
+ **Fórmula:**
54
+ ```
55
+ Defect Escape Rate = (Bugs encontrados) / (Total bugs presentes)
56
+
57
+ Ejemplo:
58
+ - Backend Dev escribió src/auth.ts
59
+ - QA ejecutó tests, encontró 4 bugs
60
+ - En producción: 1 bug adicional
61
+
62
+ Escape Rate = 1 / 5 = 20%
63
+ ```
64
+
65
+ **Tabla por agente:**
66
+ ```
67
+ AGENTE | ARCHIVOS | BUGS ENCONTRADOS | BUGS EN PROD | ESCAPE RATE
68
+ ───────────────┼──────────┼──────────────────┼─────────────┼──────────
69
+ backend-dev | 12 | 4 | 1 | 20%
70
+ frontend-dev | 8 | 0 | 0 | N/A
71
+ tester-qa | — | 4 encontrados | — | 100% accuracy
72
+ ```
73
+
74
+ ---
75
+
76
+ ## Output: Comando `/sdd.defect-report`
77
+
78
+ **Resumen ejecutivo:**
79
+ ```
80
+ DEFECT REPORT — Sesión 2026-06-14
81
+
82
+ Archivos modificados: 12
83
+ Bugs encontrados (QA): 4
84
+ Bugs en producción (post-release): 1
85
+ ─────────────────────────────────
86
+ Global Escape Rate: 20% (1 de 5)
87
+
88
+ Por agente:
89
+ backend-dev: 4 archivos, 3 bugs encontrados → 75% quality score
90
+ frontend-dev: 8 archivos, 1 bug encontrado → 88% quality score
91
+ tester-qa: Encontró 4 bugs (100% accuracy)
92
+ ```
93
+
94
+ ---
95
+
96
+ ## Implementación Técnica
97
+
98
+ ### Ledger: `.sdd/observabilidad/mutaciones.jsonl`
99
+
100
+ Append-only JSONL donde cada línea es una mutación:
101
+ ```json
102
+ {"ts":"2026-06-14T10:30:00Z","agente":"backend-dev","archivo":"src/auth.ts","tool":"Edit","tipo":"partial"}
103
+ {"ts":"2026-06-14T10:35:00Z","agente":"backend-dev","archivo":"src/auth.ts","tool":"Write","tipo":"full"}
104
+ {"ts":"2026-06-14T15:00:00Z","agente":"tester-qa","archivo":"src/auth.test.ts","tool":"Write","tipo":"full"}
105
+ ```
106
+
107
+ ### Agregación por archivo
108
+
109
+ ```javascript
110
+ const mutacionesPorArchivo = {};
111
+ for (const mut of mutaciones) {
112
+ if (!mutacionesPorArchivo[mut.archivo]) {
113
+ mutacionesPorArchivo[mut.archivo] = [];
114
+ }
115
+ mutacionesPorArchivo[mut.archivo].push(mut);
116
+ }
117
+ ```
118
+
119
+ ### Cálculo de Inestabilidad
120
+
121
+ ```
122
+ Si archivo tiene >2 mutaciones en <24h → INESTABLE
123
+ Si archivo tiene >5 mutaciones → CRÍTICO
124
+ ```
125
+
126
+ ---
127
+
128
+ ## Notas
129
+
130
+ - Fase 1 (v2.6.0): Solo tracking + informe simple
131
+ - Fase 2 (v2.6.1): Dashboard con gráficos
132
+ - Ledger append-only: nunca sobrescrito, histórico completo
133
+ - Reversible: puedes recalcular estadísticas en cualquier momento
134
+
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  description: Lee .sdd/observabilidad/consumo.jsonl y genera un reporte de actividad de agentes — invocaciones por agente, archivos tocados, picos y señales de fan-out excesivo. Referencia cruzada con orquestacion-ptc y compresion-tokens.
3
- model: claude-haiku-4-5-20251001
3
+ model: haiku
4
4
  allowed-tools: Read, Bash
5
5
  ---
6
6
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  description: Dado el estado actual del proyecto (fase, tareas pendientes, historial de consumo), estima el presupuesto de tokens por fase restante y recomienda si usar PTC paralelo o serial. Reutiliza criterios de orquestacion-ptc para la recomendación de paralelización.
3
- model: claude-haiku-4-5-20251001
3
+ model: haiku
4
4
  allowed-tools: Read
5
5
  ---
6
6
 
@@ -108,6 +108,29 @@ Para la fase `implementacion` con N tareas pendientes:
108
108
 
109
109
  ---
110
110
 
111
+ ## Señal de alerta: presupuesto proyectado < 20%
112
+
113
+ Tras calcular el peso total estimado de las fases restantes y compararlo con el presupuesto disponible (calibrado con el ledger), si **el presupuesto proyectado restante cae por debajo del 20%**, emite una alerta destacada antes de cualquier recomendación:
114
+
115
+ ```
116
+ ⚠️ ALERTA DE PRESUPUESTO — proyección < 20% disponible
117
+
118
+ Presupuesto proyectado restante: {X}% (umbral crítico: 20%)
119
+ Fases que aún no caben con el margen actual: {lista}
120
+
121
+ ACCIONES RECOMENDADAS (de mayor a menor impacto):
122
+ 1. Ejecutar /sdd.comprimir aplicar para liberar contexto ahora.
123
+ 2. Degradar modelos con effort-router en las fases restantes.
124
+ 3. Paralelizar con PTC las tareas independientes (ahorro del orquestador).
125
+ 4. Dividir la fase de implementación: ejecutar por lotes y comprimir entre lotes.
126
+ ```
127
+
128
+ Cómo se calcula el porcentaje proyectado: `(peso_disponible − peso_total_estimado_fases_restantes) / peso_disponible × 100`. Si no hay historial para fijar `peso_disponible`, usa el umbral conservador del ledger (velocidad observada × eventos restantes estimados) y marca la alerta como **estimación sin calibrar**.
129
+
130
+ La alerta es informativa y no bloquea: el usuario decide. Pero debe mostrarse SIEMPRE que la proyección cruce el umbral del 20%.
131
+
132
+ ---
133
+
111
134
  ## Output que produces
112
135
 
113
136
  ```
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  description: Genera un wireframe HTML de la pantalla P0 del MVP. Usa el DESIGN.md activo para colores, tipografía y estilo. Guarda el HTML en .sdd/diseño/ con la tool Write. Respeta {PLUGIN_DIR}/craft/anti-ai-slop.md.
3
- model: claude-sonnet-4-6
3
+ model: sonnet
4
4
  allowed-tools: Read, Write, Bash
5
5
  ---
6
6
 
@@ -1,158 +0,0 @@
1
- # sdd-figma-mcp
2
-
3
- MCP server local para integración de Figma con proyectos que usan SDD-ES. Lo usa el agente `desarrollador-frontend` para analizar el sistema de diseño del proyecto, traer componentes de Figma y generar código adaptado — sin romper lo que ya existe.
4
-
5
- ## Qué hace
6
-
7
- | Herramienta | Qué resuelve |
8
- |---|---|
9
- | `analizar_sistema_diseño` | Lee tokens, colores, tipografía y componentes del proyecto local |
10
- | `evaluar_ui_existente` | Score 0-100 + problemas + sugerencias de mejora |
11
- | `conectar_figma` | Verifica PAT y metadata del archivo |
12
- | `listar_componentes` | Lista componentes publicados en el archivo Figma |
13
- | `traer_componente` | Detalle completo de un nodo: estructura, fills, texto, dimensiones |
14
- | `mapear_estilos` | Cruza colores/tipografía de Figma con tokens del proyecto |
15
- | `generar_componente` | Código React/Vue adaptado al sistema de diseño local |
16
- | `sugerir_mejoras` | Lista priorizada de mejoras al design system |
17
-
18
- ## Instalación
19
-
20
- ### 1. Sin dependencias — listo para ejecutar
21
-
22
- No hay `npm install`. El servidor usa exclusivamente módulos built-in de Node.js (`readline`, `fs`, `path`) y `fetch` nativo (disponible desde Node 18). El `package.json` es solo un descriptor — no instala nada.
23
-
24
- ### 2. Obtener el Personal Access Token de Figma
25
-
26
- 1. Abre Figma → Menú de usuario (esquina superior derecha) → **Settings**
27
- 2. Pestaña **Security** → **Personal access tokens** → **Generate new token**
28
- 3. Dale un nombre (ej. `sdd-figma-mcp`) y copia el token
29
-
30
- ### 3. Definir FIGMA_PAT como variable de entorno del sistema
31
-
32
- El MCP lee el token desde `process.env.FIGMA_PAT` — **no está hardcodeado en ningún archivo de configuración** para evitar que quede en el repositorio.
33
-
34
- Claude Code hereda las variables de entorno de la sesión donde se lanza, así que basta con definirla una vez en el sistema:
35
-
36
- **Windows (PowerShell, permanente para el usuario):**
37
- ```powershell
38
- [System.Environment]::SetEnvironmentVariable("FIGMA_PAT", "tu-token-aqui", "User")
39
- # Cierra y vuelve a abrir la terminal para que tome efecto
40
- ```
41
-
42
- **Windows (solo sesión actual):**
43
- ```powershell
44
- $env:FIGMA_PAT = "tu-token-aqui"
45
- ```
46
-
47
- **macOS / Linux (permanente):**
48
- ```bash
49
- echo 'export FIGMA_PAT="tu-token-aqui"' >> ~/.zshrc # o ~/.bashrc
50
- source ~/.zshrc
51
- ```
52
-
53
- > Si el proyecto tiene un `.env` que Claude Code carga automáticamente, también puedes agregar `FIGMA_PAT=tu-token` ahí — el proceso Node lo hereda igual.
54
-
55
- ### 4. Registrar el MCP en Claude Code
56
-
57
- Agrega esto a tu `.claude/settings.json` (o `~/.claude/settings.json` para uso global):
58
-
59
- ```json
60
- {
61
- "mcpServers": {
62
- "sdd-figma": {
63
- "command": "node",
64
- "args": ["/ruta/absoluta/al/sdd-lite/mcp-figma/src/index.js"]
65
- }
66
- }
67
- }
68
- ```
69
-
70
- No hay campo `env` — el servidor hereda `FIGMA_PAT` directamente del entorno del sistema (paso 3). Así el token nunca queda registrado en ningún archivo de configuración del repositorio.
71
-
72
- > Reemplaza `/ruta/absoluta/al/sdd-lite` con la ruta real donde instalaste SDD-ES.
73
- > En Windows usa barras invertidas dobles: `"c:\\\\Users\\\\usuario\\\\sdd-lite\\\\mcp-figma\\\\src\\\\index.js"`
74
-
75
- ### 5. Verificar
76
-
77
- Reinicia Claude Code y ejecuta en cualquier proyecto frontend:
78
-
79
- ```
80
- analizar_sistema_diseño({ project_root: "." })
81
- ```
82
-
83
- Deberías ver el perfil del sistema de diseño del proyecto.
84
-
85
- ## Cómo encontrar el file_key de Figma
86
-
87
- La URL de Figma tiene este formato:
88
-
89
- ```
90
- https://www.figma.com/file/ABCDEF123456/nombre-del-archivo?node-id=0:1
91
- ^^^^^^^^^^^^
92
- este es el file_key
93
- ```
94
-
95
- ## Cómo encontrar el node_id
96
-
97
- 1. Selecciona el frame o componente en Figma
98
- 2. La URL cambia a: `?node-id=123:456`
99
- 3. Ese valor (`123:456` o en formato `123-456`) es el `node_id`
100
-
101
- ## Flujo típico de uso
102
-
103
- ```
104
- # 1. Analiza lo que ya tiene el proyecto
105
- analizar_sistema_diseño({ project_root: "/mi/proyecto" })
106
-
107
- # 2. Conecta con el archivo de Figma
108
- conectar_figma({ file_key: "ABCDEF123456" })
109
-
110
- # 3. Lista los componentes disponibles
111
- listar_componentes({ file_key: "ABCDEF123456", filter: "Button" })
112
-
113
- # 4. Trae el componente que quieres implementar
114
- traer_componente({ file_key: "ABCDEF123456", node_id: "123:456" })
115
-
116
- # 5. Verifica que los estilos tienen equivalente en el proyecto
117
- mapear_estilos({ file_key: "ABCDEF123456", node_id: "123:456", project_root: "/mi/proyecto" })
118
-
119
- # 6. Genera el código adaptado
120
- generar_componente({ file_key: "ABCDEF123456", node_id: "123:456", project_root: "/mi/proyecto" })
121
- ```
122
-
123
- ## Stacks soportados
124
-
125
- | Framework | CSS | Estado |
126
- |---|---|---|
127
- | React / Next.js | Tailwind CSS | ✅ Completo |
128
- | React / Next.js | CSS Modules | ✅ Completo |
129
- | Vue 3 | Tailwind / Scoped CSS | ✅ Completo |
130
- | React / Next.js | styled-components | ⚠️ Parcial (genera CSS Module) |
131
- | Angular | Cualquiera | ⚠️ Genera React, ajusta manualmente |
132
- | Svelte | Cualquiera | 🔜 En roadmap |
133
-
134
- ## Variables de entorno
135
-
136
- | Variable | Requerida | Descripción |
137
- |---|---|---|
138
- | `FIGMA_PAT` | Sí | Personal Access Token de Figma |
139
-
140
- ## Estructura del proyecto
141
-
142
- ```
143
- mcp-figma/
144
- ├── src/
145
- │ ├── index.js ← Servidor MCP + definición de tools
146
- │ ├── mcp.js ← Protocolo JSON-RPC 2.0 sobre stdio (sin deps)
147
- │ ├── figma-client.js ← Cliente HTTP de la API de Figma
148
- │ ├── design-system-analyzer.js ← Análisis del sistema de diseño local
149
- │ ├── style-mapper.js ← Mapeo de estilos Figma ↔ tokens locales
150
- │ └── component-generator.js ← Generación de código por framework
151
- └── package.json ← Cero dependencias — solo descriptor
152
- ```
153
-
154
- ## Limitaciones conocidas
155
-
156
- - La detección de tokens en Tailwind usa análisis estático de texto (no ejecuta el módulo), por lo que configs muy dinámicas pueden no detectarse completamente
157
- - La generación de componentes es un punto de partida — siempre revisa accesibilidad, props faltantes y manejo de estado
158
- - La API de Figma Variables (tokens del sistema de diseño de Figma) requiere plan Professional o superior
@@ -1,7 +0,0 @@
1
- {
2
- "name": "sdd-figma-mcp",
3
- "version": "1.0.0",
4
- "description": "MCP server para integración de Figma con SDD-ES — cero dependencias externas",
5
- "type": "module",
6
- "main": "src/index.js"
7
- }
@@ -1,162 +0,0 @@
1
- // @ts-check
2
- import { existsSync } from "fs";
3
- import { join } from "path";
4
-
5
- /**
6
- * @typedef {{ figmaName:string, figmaValue:string, localToken:string|null, matchType:string }} ColorMapping
7
- * @typedef {{ type:string, color?:{r:number,g:number,b:number}, fills?:any[], children?:any[], name:string, absoluteBoundingBox?:{width:number,height:number}, style?:any }} FigmaNode
8
- * @typedef {{ stack:{ framework:string, cssApproach:string, hasTokenFile:boolean }, colors:Record<string,string>, spacing:Record<string,string>, typography:{ fontFamilies:string[], fontSizes:Record<string,string>, fontWeights:Record<string,string> }, existingComponents:string[], breakpoints:Record<string,string>, shadows:Record<string,string> }} DesignProfile
9
- */
10
-
11
- /** @param {string} str */
12
- function toPascalCase(str) {
13
- return str.replace(/[^a-zA-Z0-9\s]/g, " ").split(/\s+/).filter(Boolean)
14
- .map(w => w[0].toUpperCase() + w.slice(1).toLowerCase()).join("");
15
- }
16
-
17
- /** @param {string} str */
18
- function toKebabCase(str) {
19
- return str.replace(/[^a-zA-Z0-9\s]/g, " ").split(/\s+/).filter(Boolean).join("-").toLowerCase();
20
- }
21
-
22
- /** @param {FigmaNode} node */
23
- function inferProps(node) {
24
- /** @type {Record<string,string>} */
25
- const props = {};
26
- const name = node.name.toLowerCase();
27
- const hasText = (node.children ?? []).some(/** @param {any} c */ c => c.type === "TEXT");
28
- if (hasText || /(button|label|title|heading)/.test(name)) props["children"] = "ReactNode";
29
- if (/(button|btn|cta|link)/.test(name)) { props["onClick"] = "() => void"; props["disabled"] = "boolean"; }
30
- if ((node.children ?? []).some(/** @param {any} c */ c => /(image|img|avatar)/.test(c.name.toLowerCase()))) { props["src"] = "string"; props["alt"] = "string"; }
31
- if (/(card|button|badge|chip)/.test(name)) props["variant"] = '"primary" | "secondary" | "ghost"';
32
- return props;
33
- }
34
-
35
- /**
36
- * @param {ColorMapping|null|undefined} mapping
37
- * @param {string} prefix
38
- */
39
- function resolveColorClass(mapping, prefix) {
40
- if (!mapping) return "";
41
- return mapping.localToken ? `${prefix}-[var(${mapping.localToken})]` : `${prefix}-[${mapping.figmaValue}]`;
42
- }
43
-
44
- /**
45
- * Construye el bloque CSS usando variables del proyecto o valores hex como fallback.
46
- * @param {string} selector
47
- * @param {FigmaNode} node
48
- * @param {ColorMapping[]} colorMappings
49
- * @returns {string}
50
- */
51
- function buildCSSBlock(selector, node, colorMappings) {
52
- const dims = node.absoluteBoundingBox;
53
- const lines = [];
54
-
55
- if (dims) {
56
- lines.push(` width: ${Math.round(dims.width)}px;`);
57
- lines.push(` height: ${Math.round(dims.height)}px;`);
58
- }
59
-
60
- const bgFill = (node.fills ?? []).find(/** @param {any} f */ f => f.type === "SOLID");
61
- if (bgFill?.color) {
62
- const hex = `#${Math.round(bgFill.color.r*255).toString(16).padStart(2,"0")}${Math.round(bgFill.color.g*255).toString(16).padStart(2,"0")}${Math.round(bgFill.color.b*255).toString(16).padStart(2,"0")}`;
63
- const mapping = colorMappings.find(m => m.figmaValue === hex);
64
- lines.push(` background-color: ${mapping?.localToken ? `var(${mapping.localToken})` : hex};`);
65
- }
66
-
67
- const textChild = (node.children ?? []).find(/** @param {any} c */ c => c.type === "TEXT" && c.style);
68
- if (textChild?.style) {
69
- const s = textChild.style;
70
- if (s.fontSize) lines.push(` font-size: ${s.fontSize}px;`);
71
- if (s.fontWeight) lines.push(` font-weight: ${s.fontWeight};`);
72
- if (s.lineHeightPx) lines.push(` line-height: ${Math.round(s.lineHeightPx)}px;`);
73
- }
74
-
75
- if (lines.length === 0) lines.push(" /* estilos del sistema de diseño */");
76
-
77
- return `.${selector} {\n${lines.join("\n")}\n}`;
78
- }
79
-
80
- /**
81
- * @param {FigmaNode} node
82
- * @param {string} componentName
83
- * @param {Record<string,string>} props
84
- * @param {ColorMapping[]} colorMappings
85
- * @param {string} cssApproach
86
- */
87
- function generateReact(node, componentName, props, colorMappings, cssApproach) {
88
- const warnings = [];
89
- const dims = node.absoluteBoundingBox;
90
- if (!dims) warnings.push("No se pudo leer el bounding box — dimensiones aproximadas.");
91
- if (colorMappings.some(m => m.matchType === "new")) warnings.push("Algunos colores de Figma no tienen token equivalente en el proyecto. Se usó el valor hex directo.");
92
-
93
- const hasChildren = "children" in props;
94
- const propsStr = Object.entries(props).map(([k,v]) => `${k}${v==="boolean"?"?":""}: ${v}`).join("; ");
95
- const propsKeys = Object.keys(props).join(", ") || "className";
96
- const cssClass = toKebabCase(componentName);
97
-
98
- /** @type {string|null} */
99
- let cssSnippet = null;
100
- let code = "";
101
-
102
- if (cssApproach === "tailwind") {
103
- const bgFill = (node.fills ?? []).find(/** @param {any} f */ f => f.type === "SOLID");
104
- const bgHex = bgFill?.color
105
- ? `#${Math.round(bgFill.color.r*255).toString(16).padStart(2,"0")}${Math.round(bgFill.color.g*255).toString(16).padStart(2,"0")}${Math.round(bgFill.color.b*255).toString(16).padStart(2,"0")}`
106
- : null;
107
- const bgMapping = bgHex ? colorMappings.find(m => m.figmaValue === bgHex) : null;
108
- const bgClass = bgMapping ? resolveColorClass(bgMapping, "bg") : "";
109
- code = `import React from "react";\n\ninterface ${componentName}Props {\n ${propsStr || "className?: string"}\n}\n\nexport function ${componentName}({ ${propsKeys} }: ${componentName}Props) {\n return (\n <div className="${bgClass} rounded p-4">\n ${hasChildren ? "{children}" : `{/* contenido de ${node.name} */}`}\n </div>\n );\n}\n\nexport default ${componentName};\n`;
110
-
111
- } else if (cssApproach === "css-modules") {
112
- code = `import React from "react";\nimport styles from "./${cssClass}.module.css";\n\ninterface ${componentName}Props {\n ${propsStr || "className?: string"}\n}\n\nexport function ${componentName}({ ${propsKeys} }: ${componentName}Props) {\n return (\n <div className={styles.root}>\n ${hasChildren ? "{children}" : `{/* contenido de ${node.name} */}`}\n </div>\n );\n}\n\nexport default ${componentName};\n`;
113
- cssSnippet = buildCSSBlock(".root", node, colorMappings);
114
-
115
- } else {
116
- // CSS puro — clase global, snippet CSS separado
117
- code = `import React from "react";\nimport "./${cssClass}.css";\n\ninterface ${componentName}Props {\n ${propsStr || "className?: string"}\n}\n\nexport function ${componentName}({ ${propsKeys} }: ${componentName}Props) {\n return (\n <div className="${cssClass}">\n ${hasChildren ? "{children}" : `{/* contenido de ${node.name} */}`}\n </div>\n );\n}\n\nexport default ${componentName};\n`;
118
- cssSnippet = buildCSSBlock(`.${cssClass}`, node, colorMappings);
119
- }
120
-
121
- return { filename: `${componentName}.tsx`, code, warnings, cssSnippet };
122
- }
123
-
124
- /**
125
- * @param {FigmaNode} node
126
- * @param {string} componentName
127
- * @param {Record<string,string>} props
128
- */
129
- function generateVue(node, componentName, props) {
130
- const propsBlock = Object.entries(props).map(([k,v]) => `${k}?: ${v}`).join("\n ");
131
- const hasChildren = "children" in props;
132
- const code = `<template>\n <div class="root">\n ${hasChildren ? "<slot />" : `<!-- contenido de ${node.name} -->`}\n </div>\n</template>\n\n<script setup lang="ts">\ninterface Props {\n ${propsBlock}\n}\ndefineProps<Props>();\n</script>\n\n<style scoped>\n.root {\n /* estilos del sistema de diseño */\n}\n</style>\n`;
133
- return { filename: `${componentName}.vue`, code, warnings: /** @type {string[]} */ ([]), cssSnippet: /** @type {string|null} */ (null) };
134
- }
135
-
136
- /**
137
- * @param {FigmaNode} node
138
- * @param {DesignProfile} profile
139
- * @param {ColorMapping[]} colorMappings
140
- */
141
- function generateComponent(node, profile, colorMappings) {
142
- const componentName = toPascalCase(node.name) || "FigmaComponent";
143
- const props = inferProps(node);
144
- return profile.stack.framework === "vue"
145
- ? generateVue(node, componentName, props)
146
- : generateReact(node, componentName, props, colorMappings, profile.stack.cssApproach);
147
- }
148
-
149
- /** @param {DesignProfile} profile */
150
- function suggestImprovements(profile) {
151
- const improvements = [];
152
- if (Object.keys(profile.colors).length === 0) improvements.push({ priority: "alta", area: "Tokens de color", description: "No se detectaron tokens de color", action: "Define colores como CSS variables o en tailwind.config.js" });
153
- if (!profile.stack.hasTokenFile) improvements.push({ priority: "alta", area: "Token file", description: "No existe archivo único de tokens", action: "Crea src/tokens.ts con exports de colores, tipografía y espaciado" });
154
- if (profile.existingComponents.length === 0) improvements.push({ priority: "alta", area: "Componentes base", description: "No se encontró librería de componentes", action: "Crea src/components con Button, Input, Card" });
155
- if (Object.keys(profile.spacing).length === 0) improvements.push({ priority: "media", area: "Espaciado", description: "No se detectaron tokens de espaciado", action: "Define escala de espaciado (4, 8, 16, 24, 32px)" });
156
- if (Object.keys(profile.breakpoints).length === 0) improvements.push({ priority: "media", area: "Breakpoints", description: "No se detectaron breakpoints como tokens", action: "Define breakpoints en tailwind.config.js o custom media queries" });
157
- if (profile.typography.fontFamilies.length === 0) improvements.push({ priority: "media", area: "Tipografía", description: "No se detectó fuente declarada como token", action: "Define --font-sans o fontFamily en tailwind.config.js" });
158
- if (Object.keys(profile.shadows).length === 0) improvements.push({ priority: "baja", area: "Sombras", description: "No se detectaron tokens de sombra", action: "Define sombras estándar (sm, md, lg)" });
159
- return improvements;
160
- }
161
-
162
- export { generateComponent, suggestImprovements };