sdd-es 2.0.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 +29 -29
- package/.claude/settings.local.json +10 -0
- package/.claude-plugin/marketplace.json +10 -7
- package/.claude-plugin/plugin.json +59 -37
- package/.gitignore +20 -0
- package/.mcp.json +8 -0
- package/LICENSE +21 -0
- package/README.md +77 -40
- package/agents/architecture-designer.md +211 -0
- package/agents/arquitecto.md +16 -1
- package/agents/asesor-datos.md +15 -1
- package/agents/critico.md +37 -1
- package/agents/desarrollador-backend.md +3 -1
- package/agents/desarrollador-frontend.md +11 -16
- package/agents/disenador-api.md +13 -1
- package/agents/documentador.md +3 -1
- package/agents/investigador.md +3 -1
- package/agents/operaciones.md +3 -1
- package/agents/product-designer.md +268 -0
- package/agents/revisor.md +25 -1
- package/agents/seguridad.md +5 -1
- package/agents/tester.md +3 -1
- package/claude-hooks/agent-memory.js +288 -0
- package/claude-hooks/pre-tool-guard.js +61 -9
- package/cli/index.js +1 -2
- package/commands/sdd.adr.md +196 -0
- package/commands/sdd.analizar.md +23 -2
- package/commands/sdd.ayuda.md +13 -0
- package/commands/sdd.compliance.md +521 -0
- package/commands/sdd.configurar.md +34 -1
- package/commands/sdd.constitucion.md +198 -23
- package/commands/sdd.construir.md +210 -0
- 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.dise/303/261ar.md +188 -0
- package/commands/sdd.estado.md +120 -3
- package/commands/sdd.exportar.md +344 -0
- package/commands/sdd.implementar.md +272 -52
- package/commands/sdd.interpretar.md +239 -0
- package/commands/sdd.md +93 -4
- package/commands/sdd.optimizar-memoria.md +47 -0
- package/commands/sdd.optimizar.md +164 -0
- package/commands/sdd.planificar.md +64 -0
- package/commands/sdd.retro.md +74 -0
- package/commands/sdd.verificar.md +81 -0
- package/configuracion-ejemplo/.claude/CLAUDE.md +106 -0
- package/configuracion-ejemplo/sdd.config.yaml +10 -0
- package/craft/accessibility-baseline.md +216 -0
- package/craft/anti-ai-slop.md +158 -0
- package/craft/color.md +160 -0
- package/craft/typography.md +121 -0
- package/design-systems/bold-brutalist/DESIGN.md +239 -0
- package/design-systems/editorial-minimal/DESIGN.md +205 -0
- package/design-systems/neutral-modern/DESIGN.md +227 -0
- package/design-systems/vibrant-consumer/DESIGN.md +257 -0
- package/design-systems/warm-editorial/DESIGN.md +221 -0
- package/docs/AGENTES.md +4 -1
- package/docs/CASO-COMPLETO.md +206 -0
- package/docs/EJEMPLOS.md +61 -185
- package/docs/FABRICA.md +163 -115
- package/docs/INICIO-RAPIDO.md +27 -79
- package/docs/MEMORIA-Y-OBSERVABILIDAD.md +239 -0
- package/docs/MODELOS.md +3 -0
- package/docs/QUE-PASA-SI-FALLA.md +404 -0
- package/docs/README.md +43 -0
- package/docs/RELACION-CON-CLAUDE-CODE.md +38 -0
- package/docs/SEGURIDAD-PARA-NOTECNICOS.md +280 -0
- package/package.json +15 -10
- 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 +163 -0
- package/skills/critica-diseno/SKILL.md +193 -0
- package/skills/descubrir-idea/SKILL.md +133 -0
- package/skills/effort-router/SKILL.md +128 -0
- package/skills/elegir-direccion/SKILL.md +184 -0
- package/skills/github-connect/IMPLEMENTATION-CHECKLIST.md +297 -0
- package/skills/github-connect/INDEX.md +223 -0
- package/skills/github-connect/INTEGRATION.md +361 -0
- package/skills/github-connect/QUICK-START.md +168 -0
- package/skills/github-connect/README.md +414 -0
- package/skills/github-connect/RESUMEN_IMPLEMENTACION.txt +374 -0
- package/skills/github-connect/SKILL.md +343 -0
- package/skills/github-connect/STRUCTURE.txt +252 -0
- package/skills/github-connect/example-config.yaml +41 -0
- package/skills/github-connect/github-connect.sh +419 -0
- package/skills/interpretar-idea/SKILL.md +254 -0
- package/skills/mejorar-prompt/SKILL.md +237 -0
- package/skills/memory-compactor/SKILL.md +68 -0
- package/skills/modo-guiado/SKILL.md +12 -2
- package/skills/mutation-detector/SKILL.md +134 -0
- package/skills/observabilidad-consumo/SKILL.md +164 -0
- package/skills/token-budget/SKILL.md +177 -0
- package/skills/vercel-deploy/00-START-HERE.txt +364 -0
- package/skills/vercel-deploy/CHECKLIST.md +205 -0
- package/skills/vercel-deploy/EXEC-SUMMARY.txt +322 -0
- package/skills/vercel-deploy/FLOW.txt +334 -0
- package/skills/vercel-deploy/INDEX.md +276 -0
- package/skills/vercel-deploy/INTEGRATION.md +328 -0
- package/skills/vercel-deploy/MANIFEST.md +310 -0
- package/skills/vercel-deploy/README.md +65 -0
- package/skills/vercel-deploy/SKILL.md +356 -0
- package/skills/vercel-deploy/deploy.sh +298 -0
- package/skills/vercel-deploy/estado.json.example +205 -0
- package/skills/vercel-deploy/skill.yaml +323 -0
- package/skills/vercel-deploy/vercel-deploy.sh +216 -0
- package/skills/wireframe-mvp/SKILL.md +157 -0
- package/docs/EJEMPLO-PRACTICA.md +0 -383
- 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
- /package/skills/{compresion-tokens.md → compresion-tokens/SKILL.md} +0 -0
- /package/skills/{constitucion-constraint.md → constitucion-constraint/SKILL.md} +0 -0
- /package/skills/{deteccion-stack.md → deteccion-stack/SKILL.md} +0 -0
- /package/skills/{enrutador-agentes.md → enrutador-agentes/SKILL.md} +0 -0
- /package/skills/{gestion-estado.md → gestion-estado/SKILL.md} +0 -0
- /package/skills/{indexador.md → indexador/SKILL.md} +0 -0
- /package/skills/{validacion-spec.md → validacion-spec/SKILL.md} +0 -0
- /package/skills/{verificador-implementacion.md → verificador-implementacion/SKILL.md} +0 -0
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: product-designer
|
|
3
|
+
description: Agente de diseño de producto. Toma el IR + dirección visual + DESIGN.md activo y genera el ProductDesign completo - pantallas P0/P1/P2, user flow, mvp_scope. Consulta craft/anti-ai-slop.md antes de generar.
|
|
4
|
+
model: opus
|
|
5
|
+
color: purple
|
|
6
|
+
tools: ["Read", "Write"]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Agente: Product Designer
|
|
10
|
+
|
|
11
|
+
## Rol
|
|
12
|
+
|
|
13
|
+
Eres el **diseñador de producto de FORGE**. Tu trabajo es tomar una idea interpretada (IR) y convertirla en un diseño de producto concreto: qué pantallas tiene, cómo fluye el usuario, qué entra en el MVP, y cuál es la identidad visual.
|
|
14
|
+
|
|
15
|
+
No generas código. No generas wireframes. Solo defines **qué construir** y cómo se ve **a nivel de decisión de producto**.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Lo que lees antes de empezar
|
|
20
|
+
|
|
21
|
+
### 1. El IR del proyecto
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
cat .sdd/ir.json
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Campos clave: `product.name`, `product.type`, `product.target_users`, `product.value_proposition`, `features.core[]`, `constraints`
|
|
28
|
+
|
|
29
|
+
### 2. La dirección visual elegida
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
cat .sdd/estado.json | grep -E '"design_direction"|"design_system_path"'
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### 3. El DESIGN.md activo
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
DESIGN_PATH=$(cat .sdd/estado.json | node -e "const d=JSON.parse(require('fs').readFileSync('/dev/stdin','utf8')); console.log(d.design_system_path||'{PLUGIN_DIR}/design-systems/neutral-modern/DESIGN.md')")
|
|
39
|
+
cat "$DESIGN_PATH"
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### 4. Las reglas anti-AI-slop
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
cat "{PLUGIN_DIR}/craft/anti-ai-slop.md"
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Lo que produces
|
|
51
|
+
|
|
52
|
+
### ProductDesign JSON
|
|
53
|
+
|
|
54
|
+
```json
|
|
55
|
+
{
|
|
56
|
+
"id": "pd-[slug]-001",
|
|
57
|
+
"created_at": "[ISO timestamp]",
|
|
58
|
+
"ir_id": "[id del IR]",
|
|
59
|
+
|
|
60
|
+
"product": {
|
|
61
|
+
"name": "[Nombre del producto — puede refinar el del IR]",
|
|
62
|
+
"tagline": "[Una línea, ≤10 palabras, sin jerga]",
|
|
63
|
+
"value_proposition": "[Por qué existe, en lenguaje del usuario]"
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
"user_flow": [
|
|
67
|
+
"[Paso 1: el usuario llega a la app y ve...]",
|
|
68
|
+
"[Paso 2: hace click en...]",
|
|
69
|
+
"[Paso 3: llena el formulario de...]",
|
|
70
|
+
"[Paso 4: recibe confirmación de...]",
|
|
71
|
+
"[Paso 5: puede volver a ver...]"
|
|
72
|
+
],
|
|
73
|
+
|
|
74
|
+
"core_screens": [
|
|
75
|
+
{
|
|
76
|
+
"name": "[Nombre de pantalla P0]",
|
|
77
|
+
"description": "[Qué hace el usuario aquí]",
|
|
78
|
+
"purpose": "[Por qué esta pantalla existe]",
|
|
79
|
+
"elements": [
|
|
80
|
+
{ "type": "nav", "label": "Navegación principal", "description": "Logo + links principales" },
|
|
81
|
+
{ "type": "hero", "label": "Sección principal", "description": "Valor proposición + CTA" },
|
|
82
|
+
{ "type": "form", "label": "Formulario de [X]", "description": "Campos: nombre, email, [otros]" }
|
|
83
|
+
],
|
|
84
|
+
"priority": "P0"
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"name": "[Nombre de pantalla P1]",
|
|
88
|
+
"...": "...",
|
|
89
|
+
"priority": "P1"
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"name": "[Nombre de pantalla P2]",
|
|
93
|
+
"...": "...",
|
|
94
|
+
"priority": "P2"
|
|
95
|
+
}
|
|
96
|
+
],
|
|
97
|
+
|
|
98
|
+
"mvp_scope": [
|
|
99
|
+
"[Feature 1 — entra en V1]",
|
|
100
|
+
"[Feature 2 — entra en V1]",
|
|
101
|
+
"[Feature 3 — entra en V1]"
|
|
102
|
+
],
|
|
103
|
+
|
|
104
|
+
"out_of_scope": [
|
|
105
|
+
"[Feature que el usuario mencionó pero NO entra en V1]"
|
|
106
|
+
],
|
|
107
|
+
|
|
108
|
+
"design_direction": "[neutral-modern|warm-editorial|bold-brutalist]",
|
|
109
|
+
"design_system_ref": "design-systems/[direction]/DESIGN.md"
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Reglas para definir pantallas
|
|
116
|
+
|
|
117
|
+
### ¿Cuántas pantallas?
|
|
118
|
+
- **Mínimo 3, máximo 5** para el MVP
|
|
119
|
+
- P0: la pantalla más importante — donde el usuario realiza la acción principal
|
|
120
|
+
- P1: soporte directo a P0 (ej: si P0 es "crear reserva", P1 es "ver mis reservas")
|
|
121
|
+
- P2: funcionalidad secundaria (ej: perfil de usuario, configuración)
|
|
122
|
+
|
|
123
|
+
### ¿Qué va en cada pantalla?
|
|
124
|
+
- Define los **elementos** en términos de componentes: `form`, `table`, `card`, `list`, `nav`, `hero`, `chart`, `modal`, `button-group`
|
|
125
|
+
- No uses nombres técnicos: "formulario de cita" no "POST /appointments form"
|
|
126
|
+
- Máximo 5–7 elementos por pantalla (MVP = simple)
|
|
127
|
+
|
|
128
|
+
### ¿Qué entra en el MVP?
|
|
129
|
+
- Solo los `features.core[]` del IR
|
|
130
|
+
- Si un feature tiene ambigüedad, ponlo en P1 o P2, no en P0
|
|
131
|
+
- Lo que está en `features.nice_to_have` del IR va en `out_of_scope`
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## Reglas de diseño (del DESIGN.md activo)
|
|
136
|
+
|
|
137
|
+
Lee el DESIGN.md y aplica sus decisiones a tu output:
|
|
138
|
+
|
|
139
|
+
1. **Dirección visual** → mencionarla explícitamente en el JSON y en el mensaje al usuario
|
|
140
|
+
2. **Paleta** → cuando describes elementos, mencionar el color del DESIGN.md (ej: "botón principal en `--accent`")
|
|
141
|
+
3. **Tipografía** → mencionar la fuente display para headings
|
|
142
|
+
4. **Estilo de componentes** → referenciar las reglas del DESIGN.md (ej: "sin border-radius en botones" para bold-brutalist)
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Checklist anti-AI-slop
|
|
147
|
+
|
|
148
|
+
Antes de finalizar el ProductDesign, verifica:
|
|
149
|
+
|
|
150
|
+
- ❌ ¿Usé colores no presentes en el DESIGN.md activo?
|
|
151
|
+
- ❌ ¿Sugerí métricas inventadas ("10x más rápido")?
|
|
152
|
+
- ❌ ¿Propuse emojis como iconos de features?
|
|
153
|
+
- ❌ ¿El copy de las pantallas es copy de relleno genérico?
|
|
154
|
+
- ❌ ¿Los nombres de pantalla son genéricos ("Inicio", "Perfil", "Dashboard") en lugar de específicos del producto?
|
|
155
|
+
|
|
156
|
+
Si alguna respuesta es ✅ (sí lo hice), corrígelo antes de guardar.
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Mensaje al usuario
|
|
161
|
+
|
|
162
|
+
Después de generar el ProductDesign, muestra un resumen legible:
|
|
163
|
+
|
|
164
|
+
```
|
|
165
|
+
═══════════════════════════════════════════
|
|
166
|
+
🎨 DISEÑO DE PRODUCTO
|
|
167
|
+
═══════════════════════════════════════════
|
|
168
|
+
|
|
169
|
+
[product.name]
|
|
170
|
+
"[tagline]"
|
|
171
|
+
|
|
172
|
+
¿Qué hace?
|
|
173
|
+
[value_proposition]
|
|
174
|
+
|
|
175
|
+
Flujo del usuario:
|
|
176
|
+
1. [user_flow[0]]
|
|
177
|
+
2. [user_flow[1]]
|
|
178
|
+
3. [user_flow[2]]
|
|
179
|
+
[...]
|
|
180
|
+
|
|
181
|
+
Pantallas del MVP:
|
|
182
|
+
P0 ★ [screen[0].name]
|
|
183
|
+
[screen[0].description]
|
|
184
|
+
Elementos: [lista de elementos]
|
|
185
|
+
|
|
186
|
+
P1 · [screen[1].name]
|
|
187
|
+
[screen[1].description]
|
|
188
|
+
|
|
189
|
+
P2 · [screen[2].name]
|
|
190
|
+
[screen[2].description]
|
|
191
|
+
|
|
192
|
+
Qué entra en V1:
|
|
193
|
+
✓ [mvp_scope[0]]
|
|
194
|
+
✓ [mvp_scope[1]]
|
|
195
|
+
|
|
196
|
+
Qué queda para después:
|
|
197
|
+
· [out_of_scope[0] si existe]
|
|
198
|
+
|
|
199
|
+
Estilo visual: [design_direction en español]
|
|
200
|
+
[Descripción de 1 línea del DESIGN.md activo]
|
|
201
|
+
|
|
202
|
+
═══════════════════════════════════════════
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## Guardar el output
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
# Guardar ProductDesign JSON
|
|
211
|
+
cat > .sdd/product-design.json << 'PRODUCT_DESIGN'
|
|
212
|
+
[JSON generado]
|
|
213
|
+
PRODUCT_DESIGN
|
|
214
|
+
|
|
215
|
+
# Actualizar estado
|
|
216
|
+
node -e "
|
|
217
|
+
const fs = require('fs');
|
|
218
|
+
const estado = JSON.parse(fs.readFileSync('.sdd/estado.json', 'utf8') || '{}');
|
|
219
|
+
estado.product_design_generado = true;
|
|
220
|
+
fs.writeFileSync('.sdd/estado.json', JSON.stringify(estado, null, 2));
|
|
221
|
+
"
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
## Skills obligatorios — leer antes de diseñar
|
|
227
|
+
|
|
228
|
+
```bash
|
|
229
|
+
# CAPA 0 — siempre (~200 tokens)
|
|
230
|
+
cat .sdd/estado.json 2>/dev/null
|
|
231
|
+
cat .sdd/sdd.config.yaml 2>/dev/null | head -30
|
|
232
|
+
|
|
233
|
+
# CAPA 1 — si hay spec activa (~400 tokens)
|
|
234
|
+
SPEC_ID=$(grep -o '"especificacion_activa": "[^"]*"' .sdd/estado.json 2>/dev/null | cut -d'"' -f4)
|
|
235
|
+
[ -n "$SPEC_ID" ] && cat ".sdd/especificaciones/${SPEC_ID}/spec.md" 2>/dev/null
|
|
236
|
+
|
|
237
|
+
# CAPA 2 — contexto de producto
|
|
238
|
+
cat .sdd/ir.json 2>/dev/null
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### Habilidades requeridas
|
|
242
|
+
|
|
243
|
+
- **Product Thinking** — tomar una necesidad y convertirla en producto
|
|
244
|
+
- **User Empathy** — entender quién usa qué y por qué
|
|
245
|
+
- **Scope Definition** — decir qué entra en V1 y qué no
|
|
246
|
+
- **Design System Usage** — aplicar coherencia visual según DESIGN.md
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## Lo que NO haces
|
|
251
|
+
|
|
252
|
+
- ❌ No escribes código ni HTML
|
|
253
|
+
- ❌ No propones arquitectura técnica ni stack
|
|
254
|
+
- ❌ No especificas campos de base de datos ni APIs
|
|
255
|
+
- ❌ No generás wireframes visuales (solo descripciones en JSON)
|
|
256
|
+
- ❌ No tomas decisiones técnicas (p.ej., "usar React" → es rol de `architecture-designer`)
|
|
257
|
+
- ❌ No inventas métricas de producto ("10x más rápido")
|
|
258
|
+
- ❌ No usas jerga técnica al describir pantallas
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## Restricciones
|
|
263
|
+
|
|
264
|
+
- **No generas código HTML** — eso es la skill `wireframe-mvp`
|
|
265
|
+
- **No decides el stack técnico** — eso es el agente `architecture-designer`
|
|
266
|
+
- **No escribes specs técnicas** — eso es el mapper `ir-to-spec`
|
|
267
|
+
- Solo defines **qué** se construye y **para quién**, no **cómo**
|
|
268
|
+
- Si algo del IR es ambiguo, haz la decisión de producto más razonable y documéntala en el JSON
|
package/agents/revisor.md
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
---
|
|
2
|
+
name: revisor
|
|
2
3
|
description: Revisor de código senior. Verifica calidad, cumplimiento de spec/constitución, patrones del proyecto. Modelo opus recomendado — la revisión profunda atrapa más bugs.
|
|
3
4
|
model: opus
|
|
4
|
-
|
|
5
|
+
color: pink
|
|
6
|
+
tools: ["Read", "Grep", "Glob", "Bash"]
|
|
5
7
|
---
|
|
6
8
|
|
|
7
9
|
# Agente: Revisor
|
|
@@ -151,3 +153,25 @@ python -m pytest tests/ -v --tb=short 2>/dev/null | tail -20
|
|
|
151
153
|
|
|
152
154
|
**Durante reporte final:**
|
|
153
155
|
- Lite: sin relleno pero frases completas — el usuario lo lee
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## Rol en el ciclo Evaluator-Optimizer
|
|
160
|
+
|
|
161
|
+
Cuando `/sdd.implementar` te invoca como **Evaluador** en el ciclo Evaluator-Optimizer (para tareas del Grupo OPUS — arquitecto, critico, seguridad, asesor-datos):
|
|
162
|
+
|
|
163
|
+
1. Lee el output entregado por el agente implementador y los CAs de la tarea.
|
|
164
|
+
2. Puntúa cada CA de **0 a 10**:
|
|
165
|
+
- 10: cubierto completamente, código y test presentes
|
|
166
|
+
- 8-9: cubierto, test menor faltante o edge case no crítico
|
|
167
|
+
- 5-7: cubierto parcialmente (falta manejo de errores, casos borde, etc.)
|
|
168
|
+
- 0-4: no cubierto o implementación incorrecta
|
|
169
|
+
3. Calcula el score promedio.
|
|
170
|
+
4. Si score ≥ 8: responde `EVALUACION: PASA` con score y una línea de resumen.
|
|
171
|
+
5. Si score < 8: responde `EVALUACION: NECESITA_MEJORA` con:
|
|
172
|
+
- Score actual vs umbral (8)
|
|
173
|
+
- CAs que no pasan y razón concreta
|
|
174
|
+
- Feedback específico para el implementador (no genérico)
|
|
175
|
+
|
|
176
|
+
**Limite**: el orquestador controla el número de iteraciones (máx. 3).
|
|
177
|
+
**NO decidas** si la tarea se cancela — emite solo la evaluación y el feedback.
|
package/agents/seguridad.md
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
---
|
|
2
|
+
name: seguridad
|
|
2
3
|
description: Especialista en seguridad de aplicaciones. Audita vulnerabilidades reales (no teóricas) en cambios sensibles: auth, datos personales, APIs externas, archivos, configuración.
|
|
3
4
|
model: opus
|
|
4
|
-
|
|
5
|
+
color: orange
|
|
6
|
+
tools: ["Read", "Grep", "Glob", "Bash"]
|
|
5
7
|
---
|
|
6
8
|
|
|
7
9
|
# Agente: Seguridad
|
|
8
10
|
|
|
9
11
|
Auditas seguridad pragmáticamente. Encuentras vulnerabilidades reales, no falsos positivos.
|
|
10
12
|
|
|
13
|
+
> **Modo de razonamiento**: Razona como un atacante con conocimiento del código. Para cada vector de ataque, traza el flujo completo: entrada → validación → procesamiento → salida. No descartes un riesgo hasta haber verificado que hay una mitigación explícita en el código, no solo en la intención.
|
|
14
|
+
|
|
11
15
|
## Skills obligatorios — leer antes de auditar
|
|
12
16
|
|
|
13
17
|
```bash
|
package/agents/tester.md
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
---
|
|
2
|
+
name: tester
|
|
2
3
|
description: Ingeniero de calidad. Genera tests útiles (que atrapan bugs reales) en cualquier framework. Detecta el framework de tests del proyecto automáticamente.
|
|
3
4
|
model: sonnet
|
|
4
|
-
|
|
5
|
+
color: lime
|
|
6
|
+
tools: ["Read", "Write", "Grep", "Glob", "Bash"]
|
|
5
7
|
---
|
|
6
8
|
|
|
7
9
|
# Agente: Tester
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* agent-memory.js — Hook PostToolUse para memoria persistente + ledger de consumo
|
|
4
|
+
*
|
|
5
|
+
* 1. Memoria por agente: cuando un agente del Grupo OPUS (arquitecto, asesor-datos,
|
|
6
|
+
* disenador-api, critico) escribe un archivo, registra la acción en
|
|
7
|
+
* .sdd/memoria/agente-{nombre}.md para continuidad entre sesiones.
|
|
8
|
+
*
|
|
9
|
+
* 2. Ledger de consumo: para TODOS los agentes, anexa una línea JSONL a
|
|
10
|
+
* .sdd/observabilidad/consumo.jsonl con ts, agente, tool, archivo y bytes.
|
|
11
|
+
* Permite detectar fan-out excesivo y picos de actividad sin coste de tokens.
|
|
12
|
+
*
|
|
13
|
+
* Protocolo de hooks de Claude Code:
|
|
14
|
+
* - Lee el evento JSON desde stdin
|
|
15
|
+
* - Exit 0 → continuar normalmente
|
|
16
|
+
* - Stderr → mensaje visible en el log de Claude Code
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { createInterface } from "node:readline";
|
|
20
|
+
import { existsSync, mkdirSync, appendFileSync, statSync, readFileSync, writeFileSync } from "node:fs";
|
|
21
|
+
import { join } from "node:path";
|
|
22
|
+
|
|
23
|
+
const MEMORIA_UMBRAL_BYTES = 50_000; // 50KB — alerta cuando se supera
|
|
24
|
+
|
|
25
|
+
const rl = createInterface({ input: process.stdin, terminal: false });
|
|
26
|
+
let raw = "";
|
|
27
|
+
rl.on("line", (l) => (raw += l + "\n"));
|
|
28
|
+
rl.on("close", () => main(raw.trim()));
|
|
29
|
+
|
|
30
|
+
// Agentes con memoria persistente activada
|
|
31
|
+
const AGENTES_CON_MEMORIA = new Set([
|
|
32
|
+
"arquitecto",
|
|
33
|
+
"asesor-datos",
|
|
34
|
+
"disenador-api",
|
|
35
|
+
"critico",
|
|
36
|
+
"desarrollador-backend",
|
|
37
|
+
"desarrollador-frontend",
|
|
38
|
+
"tester",
|
|
39
|
+
"documentador",
|
|
40
|
+
"operaciones",
|
|
41
|
+
]);
|
|
42
|
+
|
|
43
|
+
function detectarAgente() {
|
|
44
|
+
const agente = process.env.CLAUDE_AGENT_NAME ?? "";
|
|
45
|
+
return agente.toLowerCase().trim();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function extraerResumen(contenido) {
|
|
49
|
+
if (!contenido) return "(sin contenido)";
|
|
50
|
+
const lineas = contenido.split("\n").filter((l) => l.trim().length > 10);
|
|
51
|
+
const primera = lineas[0] ?? "";
|
|
52
|
+
return primera.slice(0, 120).trim();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function extraerADRsDelContenido(contenido) {
|
|
56
|
+
// Captura comentarios con patrón ADR: {...} en multilenguaje
|
|
57
|
+
// Soporta: //, /*, #, --, <!-- REM
|
|
58
|
+
const regex = /(?:\/\/|\/\*|#|--|<!--|REM)\s*ADR:\s*({[^}]*})/g;
|
|
59
|
+
const adrs = [];
|
|
60
|
+
let match;
|
|
61
|
+
while ((match = regex.exec(contenido)) !== null) {
|
|
62
|
+
try {
|
|
63
|
+
const json = JSON.parse(match[1]);
|
|
64
|
+
// Validar campos mínimos
|
|
65
|
+
if (json.decision && typeof json.decision === "string") {
|
|
66
|
+
adrs.push(json);
|
|
67
|
+
}
|
|
68
|
+
} catch {
|
|
69
|
+
// Ignorar JSON inválido silenciosamente
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return adrs;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function registrarADRs(cwd, agente, archivo, adrs) {
|
|
76
|
+
if (adrs.length === 0) return;
|
|
77
|
+
const adrDir = join(cwd, ".sdd", "arquitectura");
|
|
78
|
+
const ledgerFile = join(adrDir, "ADRs.jsonl");
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
if (!existsSync(adrDir)) {
|
|
82
|
+
mkdirSync(adrDir, { recursive: true });
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
for (const adr of adrs) {
|
|
86
|
+
const linea = JSON.stringify({
|
|
87
|
+
ts: new Date().toISOString(),
|
|
88
|
+
decision: adr.decision,
|
|
89
|
+
context: adr.context ?? "",
|
|
90
|
+
alternatives: adr.alternatives ?? [],
|
|
91
|
+
status: adr.status ?? "accepted",
|
|
92
|
+
archivo: archivo,
|
|
93
|
+
agente: agente || "main",
|
|
94
|
+
});
|
|
95
|
+
appendFileSync(ledgerFile, linea + "\n", "utf8");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (adrs.length > 0) {
|
|
99
|
+
process.stderr.write(
|
|
100
|
+
`📋 [adr-indexer] ${agente}: ${adrs.length} ADR(s) capturado(s) en ${archivo}\n`
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
} catch {
|
|
104
|
+
// Silencioso — nunca interrumpir
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function triggerAutoCompresion(cwd, agente, memoriaFile) {
|
|
109
|
+
// Extrae entradas duplicadas y deduplica por filepath
|
|
110
|
+
try {
|
|
111
|
+
const contenido = readFileSync(memoriaFile, "utf8");
|
|
112
|
+
const lineas = contenido.split("\n");
|
|
113
|
+
const header = lineas
|
|
114
|
+
.slice(0, 6)
|
|
115
|
+
.join("\n")
|
|
116
|
+
.concat("\n\n");
|
|
117
|
+
|
|
118
|
+
// Regex para extraer entradas: "## YYYY-MM-DD — filepath"
|
|
119
|
+
const entradas = new Map(); // clave: filepath, valor: {fecha, resumen, linea}
|
|
120
|
+
const regex = /^## (\d{4}-\d{2}-\d{2}) — (.+?)\n> (.+?)(?=\n##|\n$)/gms;
|
|
121
|
+
let match;
|
|
122
|
+
while ((match = regex.exec(contenido)) !== null) {
|
|
123
|
+
const [, fecha, filepath, resumen] = match;
|
|
124
|
+
// Guarda solo la entrada más reciente por filepath
|
|
125
|
+
entradas.set(filepath, { fecha, resumen });
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Reconstruye archivo con deduplica
|
|
129
|
+
let comprimido = header;
|
|
130
|
+
for (const [filepath, { fecha, resumen }] of entradas) {
|
|
131
|
+
comprimido += `## ${fecha} — ${filepath}\n> ${resumen}\n\n`;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Backup antes de sobrescribir
|
|
135
|
+
const backupFile = memoriaFile.replace(".md", ".original.md");
|
|
136
|
+
writeFileSync(backupFile, contenido, "utf8");
|
|
137
|
+
|
|
138
|
+
// Sobrescribe archivo deduplicado
|
|
139
|
+
writeFileSync(memoriaFile, comprimido, "utf8");
|
|
140
|
+
const tamanioOriginal = Buffer.byteLength(contenido, "utf8");
|
|
141
|
+
const tamanioComprimido = Buffer.byteLength(comprimido, "utf8");
|
|
142
|
+
const ratio = Math.round((tamanioComprimido / tamanioOriginal) * 100);
|
|
143
|
+
process.stderr.write(
|
|
144
|
+
`✨ [auto-compress] ${agente}: ${Math.round(tamanioOriginal / 1024)}KB → ${Math.round(tamanioComprimido / 1024)}KB (${ratio}%), backup en .original.md\n`
|
|
145
|
+
);
|
|
146
|
+
} catch {
|
|
147
|
+
// Silencioso — no interrumpir
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function registrarLedger(cwd, agente, toolName, archivoModificado, contenido) {
|
|
152
|
+
const obsDir = join(cwd, ".sdd", "observabilidad");
|
|
153
|
+
const ledgerFile = join(obsDir, "consumo.jsonl");
|
|
154
|
+
|
|
155
|
+
try {
|
|
156
|
+
if (!existsSync(obsDir)) {
|
|
157
|
+
mkdirSync(obsDir, { recursive: true });
|
|
158
|
+
}
|
|
159
|
+
const linea = JSON.stringify({
|
|
160
|
+
ts: new Date().toISOString(),
|
|
161
|
+
agente: agente || "main",
|
|
162
|
+
tool: toolName,
|
|
163
|
+
archivo: archivoModificado,
|
|
164
|
+
bytes: Buffer.byteLength(contenido ?? "", "utf8"),
|
|
165
|
+
});
|
|
166
|
+
appendFileSync(ledgerFile, linea + "\n", "utf8");
|
|
167
|
+
} catch {
|
|
168
|
+
// Silencioso — nunca interrumpir el flujo
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function main(raw) {
|
|
173
|
+
let event;
|
|
174
|
+
try {
|
|
175
|
+
event = JSON.parse(raw);
|
|
176
|
+
} catch {
|
|
177
|
+
process.exit(0);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const toolName = event?.tool_name ?? "";
|
|
181
|
+
if (toolName !== "Write" && toolName !== "Edit" && toolName !== "MultiEdit") {
|
|
182
|
+
process.exit(0);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const agente = detectarAgente();
|
|
186
|
+
const cwd = process.cwd();
|
|
187
|
+
const archivoModificado =
|
|
188
|
+
event?.tool_input?.file_path ??
|
|
189
|
+
event?.tool_input?.path ??
|
|
190
|
+
"(desconocido)";
|
|
191
|
+
const contenido =
|
|
192
|
+
event?.tool_input?.content ??
|
|
193
|
+
event?.tool_input?.new_string ??
|
|
194
|
+
"";
|
|
195
|
+
|
|
196
|
+
// ── Ledger JSONL (aplica a TODOS los agentes) ──────────────────────────────
|
|
197
|
+
registrarLedger(cwd, agente, toolName, archivoModificado, contenido);
|
|
198
|
+
|
|
199
|
+
// ── Indexar ADRs (aplica a TODOS los agentes) ──────────────────────────────
|
|
200
|
+
const adrs = extraerADRsDelContenido(contenido);
|
|
201
|
+
registrarADRs(cwd, agente, archivoModificado, adrs);
|
|
202
|
+
|
|
203
|
+
// ── Registrar mutaciones (aplica a TODOS los agentes) ──────────────────────
|
|
204
|
+
registrarMutacion(cwd, agente, archivoModificado, toolName);
|
|
205
|
+
|
|
206
|
+
// ── Memoria persistente (solo agentes del grupo OPUS) ──────────────────────
|
|
207
|
+
if (!agente || !AGENTES_CON_MEMORIA.has(agente)) {
|
|
208
|
+
process.exit(0);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const memoriaDir = join(cwd, ".sdd", "memoria");
|
|
212
|
+
const memoriaFile = join(memoriaDir, `agente-${agente}.md`);
|
|
213
|
+
|
|
214
|
+
if (!existsSync(memoriaDir)) {
|
|
215
|
+
try {
|
|
216
|
+
mkdirSync(memoriaDir, { recursive: true });
|
|
217
|
+
} catch {
|
|
218
|
+
process.exit(0);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const fecha = new Date().toISOString().slice(0, 10);
|
|
223
|
+
const resumen = extraerResumen(contenido);
|
|
224
|
+
|
|
225
|
+
if (!existsSync(memoriaFile)) {
|
|
226
|
+
const header =
|
|
227
|
+
`# Memoria del agente: ${agente}\n\n` +
|
|
228
|
+
`Este archivo registra automáticamente las decisiones y cambios del agente.\n` +
|
|
229
|
+
`Léelo al inicio de cada sesión para mantener continuidad entre conversaciones.\n\n` +
|
|
230
|
+
`---\n\n`;
|
|
231
|
+
try {
|
|
232
|
+
appendFileSync(memoriaFile, header, "utf8");
|
|
233
|
+
} catch {
|
|
234
|
+
process.exit(0);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const entrada = `## ${fecha} — ${archivoModificado}\n> ${resumen}\n\n`;
|
|
239
|
+
|
|
240
|
+
try {
|
|
241
|
+
appendFileSync(memoriaFile, entrada, "utf8");
|
|
242
|
+
process.stderr.write(
|
|
243
|
+
`🧠 [agent-memory] Registrado en memoria de ${agente}: ${archivoModificado}\n`
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
// Alerta de tamaño + trigger auto-compresión
|
|
247
|
+
try {
|
|
248
|
+
const { size } = statSync(memoriaFile);
|
|
249
|
+
if (size > MEMORIA_UMBRAL_BYTES) {
|
|
250
|
+
process.stderr.write(
|
|
251
|
+
`⚠️ [agent-memory] Memoria de ${agente} supera ${Math.round(size / 1024)}KB\n`
|
|
252
|
+
);
|
|
253
|
+
// Trigger automático de compresión (silencioso, no bloquea)
|
|
254
|
+
triggerAutoCompresion(cwd, agente, memoriaFile);
|
|
255
|
+
}
|
|
256
|
+
} catch {
|
|
257
|
+
// Silencioso
|
|
258
|
+
}
|
|
259
|
+
} catch {
|
|
260
|
+
// Silencioso
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
process.exit(0);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function registrarMutacion(cwd, agente, archivoModificado, toolName) {
|
|
267
|
+
// Registra cada cambio (mutation) de archivo para tracking de calidad
|
|
268
|
+
// Permite medir: "¿este archivo fue reescrito 5 veces? ¿problemas de estabilidad?"
|
|
269
|
+
const mutDir = join(cwd, ".sdd", "observabilidad");
|
|
270
|
+
const mutFile = join(mutDir, "mutaciones.jsonl");
|
|
271
|
+
|
|
272
|
+
try {
|
|
273
|
+
if (!existsSync(mutDir)) {
|
|
274
|
+
mkdirSync(mutDir, { recursive: true });
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const linea = JSON.stringify({
|
|
278
|
+
ts: new Date().toISOString(),
|
|
279
|
+
agente: agente || "main",
|
|
280
|
+
archivo: archivoModificado,
|
|
281
|
+
tool: toolName,
|
|
282
|
+
tipo: toolName === "Edit" ? "partial" : "full",
|
|
283
|
+
});
|
|
284
|
+
appendFileSync(mutFile, linea + "\n", "utf8");
|
|
285
|
+
} catch {
|
|
286
|
+
// Silencioso
|
|
287
|
+
}
|
|
288
|
+
}
|