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.
- package/.claude/settings.json +24 -0
- package/.claude/settings.local.json +10 -0
- package/.claude-plugin/marketplace.json +34 -0
- package/.claude-plugin/plugin.json +119 -0
- package/.gitignore +20 -0
- package/.mcp.json +8 -0
- package/README.md +27 -20
- package/agents/architecture-designer.md +37 -0
- package/agents/desarrollador-frontend.md +8 -15
- package/agents/product-designer.md +36 -0
- package/claude-hooks/agent-memory.js +137 -3
- package/claude-hooks/pre-tool-guard.js +61 -9
- package/commands/sdd.adr.md +196 -0
- package/commands/sdd.ayuda.md +13 -0
- package/commands/sdd.compliance.md +5 -0
- package/commands/sdd.configurar.md +1 -1
- package/commands/sdd.crear-mcp.md +2 -0
- package/commands/sdd.defect-report.md +134 -0
- package/commands/sdd.descubrir.md +19 -0
- package/commands/sdd.estado.md +52 -2
- package/commands/sdd.implementar.md +71 -31
- package/commands/sdd.md +23 -3
- package/commands/sdd.optimizar-memoria.md +47 -0
- package/commands/sdd.retro.md +74 -0
- package/commands/sdd.verificar.md +71 -0
- package/configuracion-ejemplo/.claude/CLAUDE.md +106 -0
- package/configuracion-ejemplo/sdd.config.yaml +10 -0
- package/docs/CASO-COMPLETO.md +206 -0
- package/docs/EJEMPLOS.md +88 -0
- package/docs/FABRICA.md +5 -6
- package/docs/INICIO-RAPIDO.md +27 -79
- package/docs/MEMORIA-Y-OBSERVABILIDAD.md +12 -10
- package/docs/README.md +43 -0
- package/docs/RELACION-CON-CLAUDE-CODE.md +38 -0
- package/package.json +11 -8
- package/plantillas/job-story-mejorar-prompt.md +107 -0
- package/presets/enterprise.yaml +6 -0
- package/presets/lean.yaml +4 -0
- package/presets/startup.yaml +6 -0
- package/skills/adr-indexer/SKILL.md +181 -0
- package/skills/cache-audit/SKILL.md +1 -1
- package/skills/critica-diseno/SKILL.md +1 -1
- package/skills/descubrir-idea/SKILL.md +1 -1
- package/skills/effort-router/SKILL.md +1 -1
- package/skills/elegir-direccion/SKILL.md +1 -1
- package/skills/interpretar-idea/SKILL.md +1 -1
- package/skills/mejorar-prompt/SKILL.md +237 -0
- package/skills/memory-compactor/SKILL.md +34 -80
- package/skills/mutation-detector/SKILL.md +134 -0
- package/skills/observabilidad-consumo/SKILL.md +1 -1
- package/skills/token-budget/SKILL.md +24 -1
- package/skills/wireframe-mvp/SKILL.md +1 -1
- package/mcp-figma/README.md +0 -158
- package/mcp-figma/package.json +0 -7
- package/mcp-figma/src/component-generator.js +0 -162
- package/mcp-figma/src/design-system-analyzer.js +0 -247
- package/mcp-figma/src/figma-client.js +0 -75
- package/mcp-figma/src/index.js +0 -114
- package/mcp-figma/src/mcp.js +0 -97
- package/mcp-figma/src/style-mapper.js +0 -85
|
@@ -1,114 +1,68 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
model: claude-haiku-4-5
|
|
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
|
-
|
|
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
|
-
##
|
|
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
|
-
**
|
|
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
|
-
##
|
|
21
|
+
## Qué Hace
|
|
40
22
|
|
|
41
|
-
###
|
|
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
|
-
###
|
|
26
|
+
### Compresión por Caveman (Nivel Full)
|
|
27
|
+
Aplica diccionario de reemplazos: `CREATE TABLE` → `CT`, `SELECT * FROM` → `SF`, etc.
|
|
56
28
|
|
|
57
|
-
|
|
29
|
+
### Backup Automático
|
|
30
|
+
Crea `.original.md` antes de comprimir.
|
|
58
31
|
|
|
59
|
-
|
|
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
|
-
|
|
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
|
-
|
|
36
|
+
**Antes:** `.sdd/memoria/agente-arquitecto.md = 150KB (80 entradas)`
|
|
76
37
|
|
|
77
|
-
|
|
78
|
-
# Memoria del agente: {nombre}
|
|
38
|
+
**Después:** `.sdd/memoria/agente-arquitecto.md = 15KB (8 entradas únicas)`
|
|
79
39
|
|
|
80
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
54
|
+
## API (Internal)
|
|
91
55
|
|
|
56
|
+
```javascript
|
|
57
|
+
triggerAutoCompresion(cwd, agente, memoriaFile)
|
|
92
58
|
```
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
-
|
|
111
|
-
-
|
|
112
|
-
-
|
|
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:
|
|
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:
|
|
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:
|
|
3
|
+
model: sonnet
|
|
4
4
|
allowed-tools: Read, Write, Bash
|
|
5
5
|
---
|
|
6
6
|
|
package/mcp-figma/README.md
DELETED
|
@@ -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
|
package/mcp-figma/package.json
DELETED
|
@@ -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 };
|