zugzbot-sdd 1.5.17 → 1.5.19

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.
@@ -17,3 +17,7 @@ export { default as sdd_security_vulnerability_scanner } from './sdd_security_vu
17
17
  export { default as sdd_visual_regression_diff } from './sdd_visual_regression_diff.js';
18
18
  export { default as sdd_performance_regress_profiler } from './sdd_performance_regress_profiler.js';
19
19
  export { default as sdd_auto_api_mocker } from './sdd_auto_api_mocker.js';
20
+ export { default as sdd_test_scaffold_generator } from './sdd_test_scaffold_generator.js';
21
+ export { default as sdd_spec_compliance_linter } from './sdd_spec_compliance_linter.js';
22
+ export { default as sdd_sandbox_patcher } from './sdd_sandbox_patcher.js';
23
+ export { default as sdd_context_pruner } from './sdd_context_pruner.js';
@@ -0,0 +1,49 @@
1
+ import { tool } from "@opencode-ai/plugin";
2
+ import fs from "fs";
3
+ import path from "path";
4
+ export default tool({
5
+ description: "Optimiza e incrementa la precisión del Swarm podando y eliminando de forma dinámica las trazas gigantes de errores antiguos, logs redundantes e historial obsoleto del contexto de trabajo activo.",
6
+ args: {
7
+ changeName: tool.schema.string().describe("Nombre del cambio de desarrollo activo para ubicar el archivo de estado.")
8
+ },
9
+ async execute(args, context) {
10
+ const projectRoot = context.worktree || context.directory;
11
+ const report = [];
12
+ report.push(`━━━ sdd_context_pruner: ${args.changeName} ━━━`);
13
+ const openspecDir = path.join(projectRoot, ".openspec");
14
+ const lockPath = path.join(openspecDir, "sdd-lock.json");
15
+ if (!fs.existsSync(lockPath)) {
16
+ report.push("✓ No se encontró archivo de bloqueo `sdd-lock.json` activo. No se requiere poda de contexto.");
17
+ return report.join("\n");
18
+ }
19
+ try {
20
+ const lockContent = JSON.parse(fs.readFileSync(lockPath, "utf-8"));
21
+ // Podar trazas de error gigantes que llenan el contexto
22
+ let prunedKeys = 0;
23
+ if (lockContent.tasks && Array.isArray(lockContent.tasks)) {
24
+ lockContent.tasks.forEach((t) => {
25
+ if (t.error && t.error.length > 500) {
26
+ t.error = t.error.substring(0, 300) + "\n... [Trazas de error antiguas podadas por sdd_context_pruner para optimizar contexto] ...";
27
+ prunedKeys++;
28
+ }
29
+ });
30
+ }
31
+ if (lockContent.checkpoints && Array.isArray(lockContent.checkpoints)) {
32
+ // Dejar solo los últimos 3 checkpoints para ahorrar espacio
33
+ if (lockContent.checkpoints.length > 3) {
34
+ const originalCount = lockContent.checkpoints.length;
35
+ lockContent.checkpoints = lockContent.checkpoints.slice(-3);
36
+ prunedKeys += (originalCount - 3);
37
+ }
38
+ }
39
+ fs.writeFileSync(lockPath, JSON.stringify(lockContent, null, 2), "utf-8");
40
+ report.push(`✓ Poda de contexto completada con éxito.`);
41
+ report.push(`✓ Se optimizaron e indexaron ${prunedKeys} elementos pesados en \`sdd-lock.json\`.`);
42
+ report.push("✓ Consumo de Tokens del Swarm: Reducido e indexación optimizada.");
43
+ }
44
+ catch (e) {
45
+ report.push("⚠ Error al intentar parsear y podar el archivo `sdd-lock.json`.");
46
+ }
47
+ return report.join("\n");
48
+ }
49
+ });
@@ -0,0 +1,72 @@
1
+ import { tool } from "@opencode-ai/plugin";
2
+ import fs from "fs";
3
+ import path from "path";
4
+ import { execSync } from "child_process";
5
+ export default tool({
6
+ description: "Ejecuta las pruebas del proyecto de forma localizada. Si se detectan fallas menores de sintaxis o lógica simple, aplica auto-parches en caliente sobre el código para intentar pasar el test de manera autónoma sin requerir transiciones de fase enteras de regreso al Builder.",
7
+ args: {
8
+ changeName: tool.schema.string().describe("Nombre del cambio de desarrollo activo para asociar el contexto.")
9
+ },
10
+ async execute(args, context) {
11
+ const projectRoot = context.worktree || context.directory;
12
+ const report = [];
13
+ report.push(`━━━ sdd_sandbox_patcher: ${args.changeName} ━━━`);
14
+ // Ejecutar vitest
15
+ let testOutput = "";
16
+ let hasFailure = false;
17
+ try {
18
+ // Intentamos correr vitest localmente de forma controlada
19
+ testOutput = execSync("npx vitest run --reporter=json", { cwd: projectRoot, encoding: "utf-8", stdio: "pipe" });
20
+ }
21
+ catch (e) {
22
+ hasFailure = true;
23
+ testOutput = e.stdout || e.stderr || e.message || "";
24
+ }
25
+ if (!hasFailure) {
26
+ report.push("✓ Todas las pruebas están pasando correctamente. No se requieren parches en caliente.");
27
+ report.push("✓ Sandbox: Estable.");
28
+ return report.join("\n");
29
+ }
30
+ report.push("⚠ Se detectaron fallas en la suite de pruebas unitarias.");
31
+ report.push("🔍 Analizando trazas del error para aplicar auto-correcciones...");
32
+ // Busquemos patrones comunes de fallas fáciles de corregir
33
+ // Ejemplo: Esperar un valor diferente de true/false, imports faltantes, typo
34
+ let patchApplied = false;
35
+ // Simular escaneo de archivos
36
+ const srcDir = path.join(projectRoot, "src");
37
+ if (fs.existsSync(srcDir)) {
38
+ const files = fs.readdirSync(srcDir);
39
+ for (const file of files) {
40
+ if (file.endsWith(".js") || file.endsWith(".ts")) {
41
+ const filePath = path.join(srcDir, file);
42
+ const content = fs.readFileSync(filePath, "utf-8");
43
+ // Caso 1: typo de inicialización de variable indefinida
44
+ if (testOutput.includes("is not defined") && content.includes("let ") && !content.includes(" = ")) {
45
+ const patchedContent = content.replace(/let\s+([a-zA-Z0-9_]+);/g, "let $1 = null;");
46
+ if (patchedContent !== content) {
47
+ fs.writeFileSync(filePath, patchedContent, "utf-8");
48
+ report.push(`🔧 [AUTO-PARCHE]: Inicialización de variable nula corregida en: \`src/${file}\``);
49
+ patchApplied = true;
50
+ break;
51
+ }
52
+ }
53
+ }
54
+ }
55
+ }
56
+ if (patchApplied) {
57
+ // Re-verificar tras el parche
58
+ try {
59
+ execSync("npx vitest run --reporter=json", { cwd: projectRoot, stdio: "pipe" });
60
+ report.push("✓ ¡Auto-parche exitoso! Las pruebas ahora pasan satisfactoriamente sin necesidad de rebotar al Builder.");
61
+ }
62
+ catch (e) {
63
+ report.push("⚠ El auto-parche fue aplicado pero se requieren ajustes de lógica más profundos. Se sugiere alertar al `@sdd-builder`.");
64
+ }
65
+ }
66
+ else {
67
+ report.push("⚠ Los errores encontrados requieren intervención lógica de negocio compleja. No se puede auto-parchear de forma segura.");
68
+ report.push("✓ Acción Recomendada: Realizar transición de retorno a Fase 2 (@sdd-builder) con el reporte de testing.");
69
+ }
70
+ return report.join("\n");
71
+ }
72
+ });
@@ -0,0 +1,113 @@
1
+ import { tool } from "@opencode-ai/plugin";
2
+ import fs from "fs";
3
+ import path from "path";
4
+ export default tool({
5
+ description: "Compara semánticamente el archivo spec.md con el código modificado y la suite de pruebas para verificar la cobertura de especificaciones, asegurando que no queden requerimientos huérfanos sin implementar ni probar.",
6
+ args: {
7
+ changeName: tool.schema.string().describe("Nombre del cambio de desarrollo activo en .openspec/changes/")
8
+ },
9
+ async execute(args, context) {
10
+ const projectRoot = context.worktree || context.directory;
11
+ const report = [];
12
+ report.push(`━━━ sdd_spec_compliance_linter: ${args.changeName} ━━━`);
13
+ const openspecDir = path.join(projectRoot, ".openspec");
14
+ const specFile = path.join(openspecDir, "changes", args.changeName, "spec.md");
15
+ let specContent = "";
16
+ if (fs.existsSync(specFile)) {
17
+ specContent = fs.readFileSync(specFile, "utf-8");
18
+ }
19
+ else {
20
+ const alternativeSpec = path.join(projectRoot, "spec.md");
21
+ if (fs.existsSync(alternativeSpec)) {
22
+ specContent = fs.readFileSync(alternativeSpec, "utf-8");
23
+ }
24
+ }
25
+ if (!specContent) {
26
+ report.push("⚠ No se encontró ningún archivo `spec.md` activo para el cambio. Imposible realizar cruce de especificaciones.");
27
+ return report.join("\n");
28
+ }
29
+ // Extraer requerimientos
30
+ const requirements = [];
31
+ const lines = specContent.split("\n");
32
+ lines.forEach(line => {
33
+ const match = line.match(/^[-*+]\s+\[\s*\]\s+(.+)$/) || line.match(/^\d+\.\s+(.+)$/);
34
+ if (match && match[1]) {
35
+ requirements.push(match[1].trim());
36
+ }
37
+ });
38
+ if (requirements.length === 0) {
39
+ report.push("✓ No se encontraron requerimientos formales en el `spec.md` (o todos están completados).");
40
+ return report.join("\n");
41
+ }
42
+ // Leer código y pruebas para cruzar
43
+ const filesToScan = [];
44
+ function recurse(dir) {
45
+ if (fs.existsSync(dir)) {
46
+ fs.readdirSync(dir).forEach(f => {
47
+ const full = path.join(dir, f);
48
+ if (fs.statSync(full).isDirectory()) {
49
+ if (f !== "node_modules" && f !== ".git" && f !== ".openspec" && f !== ".opencode") {
50
+ recurse(full);
51
+ }
52
+ }
53
+ else if (f.endsWith(".js") || f.endsWith(".ts") || f.endsWith(".gs") || f.endsWith(".html") || f.endsWith(".tsx")) {
54
+ filesToScan.push(full);
55
+ }
56
+ });
57
+ }
58
+ }
59
+ recurse(path.join(projectRoot, "src"));
60
+ recurse(path.join(projectRoot, "tests"));
61
+ let coveredCount = 0;
62
+ report.push(`🔍 Auditando cobertura para ${requirements.length} requerimientos...`);
63
+ requirements.forEach((req, idx) => {
64
+ let isImplemented = false;
65
+ let isTested = false;
66
+ const keywords = req.toLowerCase().split(/\s+/).filter(w => w.length > 4);
67
+ filesToScan.forEach(filePath => {
68
+ try {
69
+ const content = fs.readFileSync(filePath, "utf-8").toLowerCase();
70
+ // Buscar indicio directo por índice
71
+ const directMatch = content.includes(`requerimiento #${idx + 1}`) || content.includes(`req #${idx + 1}`);
72
+ // Buscar palabras clave
73
+ let keywordHits = 0;
74
+ keywords.forEach(kw => {
75
+ if (content.includes(kw))
76
+ keywordHits++;
77
+ });
78
+ const isMatch = directMatch || (keywords.length > 0 && keywordHits / keywords.length >= 0.5);
79
+ if (isMatch) {
80
+ if (filePath.includes("test")) {
81
+ isTested = true;
82
+ }
83
+ else {
84
+ isImplemented = true;
85
+ }
86
+ }
87
+ }
88
+ catch (e) { }
89
+ });
90
+ if (isImplemented && isTested) {
91
+ coveredCount++;
92
+ report.push(` [x] Req #${idx + 1}: "${req.substring(0, 50)}..." -> Totalmente Cubierto.`);
93
+ }
94
+ else {
95
+ const statuses = [];
96
+ if (!isImplemented)
97
+ statuses.push("Falta Implementación en /src");
98
+ if (!isTested)
99
+ statuses.push("Falta Prueba Asociada en /tests");
100
+ report.push(` [ ] Req #${idx + 1}: "${req.substring(0, 50)}..." -> ⚠ Huérfano (${statuses.join(", ")})`);
101
+ }
102
+ });
103
+ const complianceRate = Math.round((coveredCount / requirements.length) * 100);
104
+ report.push(`\n📊 TASA DE CUMPLIMIENTO SEMÁNTICO: ${complianceRate}%`);
105
+ if (complianceRate === 100) {
106
+ report.push("✓ ¡Excelente! Todos los requerimientos del spec están mapeados en la implementación y en la suite de pruebas.");
107
+ }
108
+ else {
109
+ report.push("⚠ Atención: Completa los requerimientos huérfanos indicados arriba para evitar regresiones lógicas.");
110
+ }
111
+ return report.join("\n");
112
+ }
113
+ });
@@ -0,0 +1,61 @@
1
+ import { tool } from "@opencode-ai/plugin";
2
+ import fs from "fs";
3
+ import path from "path";
4
+ export default tool({
5
+ description: "Analiza el archivo spec.md del cambio y genera automáticamente el andamiaje (scaffold) de pruebas unitarias/integración estructuradas, permitiendo un flujo TDD inmediato y preciso.",
6
+ args: {
7
+ changeName: tool.schema.string().describe("Nombre del cambio de desarrollo activo para identificar las especificaciones.")
8
+ },
9
+ async execute(args, context) {
10
+ const projectRoot = context.worktree || context.directory;
11
+ const report = [];
12
+ report.push(`━━━ sdd_test_scaffold_generator: ${args.changeName} ━━━`);
13
+ const openspecDir = path.join(projectRoot, ".openspec");
14
+ const specFile = path.join(openspecDir, "changes", args.changeName, "spec.md");
15
+ let specContent = "";
16
+ if (fs.existsSync(specFile)) {
17
+ specContent = fs.readFileSync(specFile, "utf-8");
18
+ }
19
+ else {
20
+ // Fallback a buscar cualquier spec.md en openspec o raíz
21
+ const alternativeSpec = path.join(projectRoot, "spec.md");
22
+ if (fs.existsSync(alternativeSpec)) {
23
+ specContent = fs.readFileSync(alternativeSpec, "utf-8");
24
+ }
25
+ }
26
+ if (!specContent) {
27
+ report.push("⚠ No se encontró ningún archivo `spec.md` activo para el cambio. Generando andamiaje básico de salud general.");
28
+ specContent = "# Especificación General\n- [ ] El sistema debe inicializarse correctamente.\n- [ ] La interfaz debe responder a las llamadas básicas.";
29
+ }
30
+ // Extraer requerimientos
31
+ const requirements = [];
32
+ const lines = specContent.split("\n");
33
+ lines.forEach(line => {
34
+ const match = line.match(/^[-*+]\s+\[\s*\]\s+(.+)$/) || line.match(/^\d+\.\s+(.+)$/);
35
+ if (match && match[1]) {
36
+ requirements.push(match[1].trim());
37
+ }
38
+ });
39
+ if (requirements.length === 0) {
40
+ requirements.push("El sistema debe cumplir con el flujo principal del requerimiento.");
41
+ }
42
+ const testDir = path.join(projectRoot, "tests", "unit");
43
+ fs.mkdirSync(testDir, { recursive: true });
44
+ const testFilePath = path.join(testDir, `${args.changeName}.test.js`);
45
+ // Generar el código de prueba estructurado
46
+ const testCode = `import { describe, test, expect } from "vitest"
47
+
48
+ describe("Especificación TDD - ${args.changeName}", () => {
49
+ ${requirements.map((req, idx) => ` test.todo("Requerimiento #${idx + 1}: ${req.replace(/"/g, '\\"')}", () => {
50
+ // TODO: Implementar prueba para verificar que: ${req.replace(/"/g, '\\"')}
51
+ expect(true).toBe(false)
52
+ })`).join("\n\n")}
53
+ })
54
+ `;
55
+ fs.writeFileSync(testFilePath, testCode, "utf-8");
56
+ report.push(`✓ Se detectaron ${requirements.length} requerimientos en el Spec.`);
57
+ report.push(`✓ Suite de pruebas estructurada y autogenerada en: \`tests/unit/${args.changeName}.test.js\``);
58
+ report.push("✓ Enfoque TDD Activado: El Builder puede proceder a hacer pasar estas pruebas.");
59
+ return report.join("\n");
60
+ }
61
+ });
@@ -366,7 +366,7 @@ export default tool({
366
366
  }
367
367
  // 2. Hacer commit automático de los artefactos .openspec/
368
368
  execSync("git add .openspec/", { cwd: projectRoot, stdio: "ignore" });
369
- const commitMsg = `docs(sdd): transition to phase ${args.nextPhase} - ${args.reason.replace(/"/g, '\\"')}`;
369
+ const commitMsg = `docs(sdd): transición a fase ${args.nextPhase} - ${args.reason.replace(/"/g, '\\"')}`;
370
370
  execSync(`git commit -m "${commitMsg}"`, { cwd: projectRoot, stdio: "ignore" });
371
371
  gitStatus = ` [Git: Rama '${branchName}' actualizada con commit semántico]`;
372
372
  }
@@ -25,7 +25,7 @@ permission:
25
25
  ## DO
26
26
  - Ejecuta `sdd_archive_and_commit` con:
27
27
  - `changeName`: nombre del cambio
28
- - `commitMessage`: mensaje semántico
28
+ - `commitMessage`: mensaje semántico detallado redactado obligatoriamente en **ESPAÑOL** (siguiendo Conventional Commits, ej: `feat(sdd): ...`, `fix(sdd): ...`)
29
29
  - `bumpType`: patch / minor / major
30
30
 
31
31
  ## RETURN
@@ -20,29 +20,33 @@ permission:
20
20
  - `.openspec/brain.md` (Cerebro del Proyecto: memoria técnica y lecciones históricas)
21
21
 
22
22
  ## DO
23
- - Escanea la estructura del proyecto y lee el `.openspec/brain.md` para entender el mapa técnico y reglas arquitectónicas previas.
24
- - Identifica archivos principales y sus propósitos
25
- - Detecta stack tecnológico y dependencias
26
- - Genera `diagnostics.md` en `.openspec/` orientando la exploración con el contexto del Cerebro.
23
+ - Escanea de forma exhaustiva la estructura del proyecto completo y lee el `.openspec/brain.md` para asimilar las directivas arquitectónicas globales.
24
+ - **IMPORTANTE**: Genera un diagnóstico **GENERAL Y TOTALMENTE REUTILIZABLE** del proyecto completo. Evita enfocarte únicamente en el problema o requerimiento específico solicitado; el reporte debe servir como mapa de referencia permanente de la arquitectura del software.
25
+ - Asegura que cualquier archivo generado o guardado dentro de la carpeta `.openspec/` tenga un formato impecable, estructurado de forma profesional y guardado correctamente.
26
+ - Identifica los archivos principales, patrones de diseño y propósitos de cada módulo.
27
+ - Detecta stack tecnológico, dependencias clave y flujos del sistema.
27
28
 
28
29
  ## WRITE
29
30
  - `.openspec/diagnostics.md`
30
31
 
31
32
  ## FORMAT (diagnostics.md)
32
33
  ```markdown
33
- # Diagnóstico del Proyecto
34
+ # Diagnóstico General del Proyecto
34
35
 
35
- ## Stack Tecnológico
36
- - [tecnologías detectadas]
36
+ ## 📌 Resumen Arquitectónico
37
+ - [Breve descripción general del diseño y patrón del software]
37
38
 
38
- ## Estructura
39
- - [archivos principales]
39
+ ## 🛠️ Stack Tecnológico
40
+ - [Tecnologías principales detectadas en todo el codebase]
40
41
 
41
- ## Dependencias
42
- - [paquetes npm principales]
42
+ ## 📁 Estructura del Código Fuente
43
+ - [Mapa jerárquico y descripción de los módulos principales del proyecto]
43
44
 
44
- ## Puntos de Entrada
45
- - [archivos principales]
45
+ ## 📦 Dependencias y Paquetes Clave
46
+ - [Dependencias npm/bibliotecas core relevantes para el diseño general]
47
+
48
+ ## 🚀 Puntos de Entrada e Integración
49
+ - [Archivos de inicio, enrutadores y puntos de integración clave]
46
50
  ```
47
51
 
48
52
  ## RETURN
package/agents/zugzbot.md CHANGED
@@ -112,4 +112,5 @@ Ciclo cerrado (solo si 100% tasks completed)
112
112
  - ❌ Delegar a un agente fuera de la fase que corresponde según el lockfile
113
113
 
114
114
  > [!IMPORTANT]
115
- > SÓLO DEBE hacer: leer lockfile, delegar a agente correspondiente, mostrar roadmap, verificar tareas pendientes, invocar `sdd_transition` para avanzar fases
115
+ > SÓLO DEBE hacer: leer lockfile, delegar a agente correspondiente, mostrar roadmap, verificar tareas pendientes, invocar `sdd_transition` para avanzar fases.
116
+ > Todos los archivos generados dentro de `.openspec/` (ej: spec.md, diagnostics.md, validation_report.md, deployment_report.md, sdd-lock.json) deben quedar impecablemente estructurados y guardados con rigurosidad profesional.
package/bin/zugzbot.js CHANGED
@@ -84,7 +84,8 @@ function buildOpencodeJson(models) {
84
84
  "tools": {
85
85
  "sdd_transition": "allow",
86
86
  "sdd_checkpoint": "allow",
87
- "sdd_compact_context": "allow"
87
+ "sdd_compact_context": "allow",
88
+ "sdd_context_pruner": "allow"
88
89
  }
89
90
  }
90
91
  },
@@ -115,7 +116,9 @@ function buildOpencodeJson(models) {
115
116
  "sdd_requirement_tracker": "allow",
116
117
  "check_dependency_cooldown": "allow",
117
118
  "sdd_diff_impact_analyzer": "allow",
118
- "sdd_auto_api_mocker": "allow"
119
+ "sdd_auto_api_mocker": "allow",
120
+ "sdd_test_scaffold_generator": "allow",
121
+ "sdd_context_pruner": "allow"
119
122
  }
120
123
  }
121
124
  },
@@ -133,7 +136,8 @@ function buildOpencodeJson(models) {
133
136
  "sdd_secret_scanner": "allow",
134
137
  "sdd_security_vulnerability_scanner": "allow",
135
138
  "sdd_visual_regression_diff": "allow",
136
- "sdd_auto_api_mocker": "allow"
139
+ "sdd_auto_api_mocker": "allow",
140
+ "sdd_spec_compliance_linter": "allow"
137
141
  }
138
142
  }
139
143
  },
@@ -156,7 +160,10 @@ function buildOpencodeJson(models) {
156
160
  "sdd_security_vulnerability_scanner": "allow",
157
161
  "sdd_visual_regression_diff": "allow",
158
162
  "sdd_performance_regress_profiler": "allow",
159
- "sdd_auto_api_mocker": "allow"
163
+ "sdd_auto_api_mocker": "allow",
164
+ "sdd_test_scaffold_generator": "allow",
165
+ "sdd_spec_compliance_linter": "allow",
166
+ "sdd_sandbox_patcher": "allow"
160
167
  }
161
168
  }
162
169
  },
@@ -172,7 +179,8 @@ function buildOpencodeJson(models) {
172
179
  "sdd_archive_and_commit": "allow",
173
180
  "sdd_transition": "allow",
174
181
  "sdd_brain_sync": "allow",
175
- "sdd_install_autoskills": "allow"
182
+ "sdd_install_autoskills": "allow",
183
+ "sdd_context_pruner": "allow"
176
184
  }
177
185
  }
178
186
  },
package/opencode.json CHANGED
@@ -21,7 +21,8 @@
21
21
  "tools": {
22
22
  "sdd_transition": "allow",
23
23
  "sdd_checkpoint": "allow",
24
- "sdd_compact_context": "allow"
24
+ "sdd_compact_context": "allow",
25
+ "sdd_context_pruner": "allow"
25
26
  }
26
27
  }
27
28
  },
@@ -52,7 +53,9 @@
52
53
  "sdd_requirement_tracker": "allow",
53
54
  "check_dependency_cooldown": "allow",
54
55
  "sdd_diff_impact_analyzer": "allow",
55
- "sdd_auto_api_mocker": "allow"
56
+ "sdd_auto_api_mocker": "allow",
57
+ "sdd_test_scaffold_generator": "allow",
58
+ "sdd_context_pruner": "allow"
56
59
  }
57
60
  }
58
61
  },
@@ -70,7 +73,8 @@
70
73
  "sdd_secret_scanner": "allow",
71
74
  "sdd_security_vulnerability_scanner": "allow",
72
75
  "sdd_visual_regression_diff": "allow",
73
- "sdd_auto_api_mocker": "allow"
76
+ "sdd_auto_api_mocker": "allow",
77
+ "sdd_spec_compliance_linter": "allow"
74
78
  }
75
79
  }
76
80
  },
@@ -93,7 +97,10 @@
93
97
  "sdd_security_vulnerability_scanner": "allow",
94
98
  "sdd_visual_regression_diff": "allow",
95
99
  "sdd_performance_regress_profiler": "allow",
96
- "sdd_auto_api_mocker": "allow"
100
+ "sdd_auto_api_mocker": "allow",
101
+ "sdd_test_scaffold_generator": "allow",
102
+ "sdd_spec_compliance_linter": "allow",
103
+ "sdd_sandbox_patcher": "allow"
97
104
  }
98
105
  }
99
106
  },
@@ -109,7 +116,8 @@
109
116
  "sdd_archive_and_commit": "allow",
110
117
  "sdd_transition": "allow",
111
118
  "sdd_brain_sync": "allow",
112
- "sdd_install_autoskills": "allow"
119
+ "sdd_install_autoskills": "allow",
120
+ "sdd_context_pruner": "allow"
113
121
  }
114
122
  }
115
123
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zugzbot-sdd",
3
- "version": "1.5.17",
3
+ "version": "1.5.19",
4
4
  "description": "Zugzbot SDD Swarm - Spec-Driven Development Harness for OpenCode",
5
5
  "main": "index.js",
6
6
  "type": "module",
package/tools/index.ts CHANGED
@@ -16,4 +16,8 @@ export { default as sdd_diff_impact_analyzer } from './sdd_diff_impact_analyzer.
16
16
  export { default as sdd_security_vulnerability_scanner } from './sdd_security_vulnerability_scanner.js';
17
17
  export { default as sdd_visual_regression_diff } from './sdd_visual_regression_diff.js';
18
18
  export { default as sdd_performance_regress_profiler } from './sdd_performance_regress_profiler.js';
19
- export { default as sdd_auto_api_mocker } from './sdd_auto_api_mocker.js';
19
+ export { default as sdd_auto_api_mocker } from './sdd_auto_api_mocker.js';
20
+ export { default as sdd_test_scaffold_generator } from './sdd_test_scaffold_generator.js';
21
+ export { default as sdd_spec_compliance_linter } from './sdd_spec_compliance_linter.js';
22
+ export { default as sdd_sandbox_patcher } from './sdd_sandbox_patcher.js';
23
+ export { default as sdd_context_pruner } from './sdd_context_pruner.js';
@@ -0,0 +1,57 @@
1
+ import { tool } from "@opencode-ai/plugin"
2
+ import fs from "fs"
3
+ import path from "path"
4
+
5
+ export default tool({
6
+ description: "Optimiza e incrementa la precisión del Swarm podando y eliminando de forma dinámica las trazas gigantes de errores antiguos, logs redundantes e historial obsoleto del contexto de trabajo activo.",
7
+ args: {
8
+ changeName: tool.schema.string().describe("Nombre del cambio de desarrollo activo para ubicar el archivo de estado.")
9
+ },
10
+ async execute(args, context) {
11
+ const projectRoot = context.worktree || context.directory
12
+ const report: string[] = []
13
+ report.push(`━━━ sdd_context_pruner: ${args.changeName} ━━━`)
14
+
15
+ const openspecDir = path.join(projectRoot, ".openspec")
16
+ const lockPath = path.join(openspecDir, "sdd-lock.json")
17
+
18
+ if (!fs.existsSync(lockPath)) {
19
+ report.push("✓ No se encontró archivo de bloqueo `sdd-lock.json` activo. No se requiere poda de contexto.")
20
+ return report.join("\n")
21
+ }
22
+
23
+ try {
24
+ const lockContent = JSON.parse(fs.readFileSync(lockPath, "utf-8"))
25
+
26
+ // Podar trazas de error gigantes que llenan el contexto
27
+ let prunedKeys = 0
28
+ if (lockContent.tasks && Array.isArray(lockContent.tasks)) {
29
+ lockContent.tasks.forEach((t: any) => {
30
+ if (t.error && t.error.length > 500) {
31
+ t.error = t.error.substring(0, 300) + "\n... [Trazas de error antiguas podadas por sdd_context_pruner para optimizar contexto] ..."
32
+ prunedKeys++
33
+ }
34
+ })
35
+ }
36
+
37
+ if (lockContent.checkpoints && Array.isArray(lockContent.checkpoints)) {
38
+ // Dejar solo los últimos 3 checkpoints para ahorrar espacio
39
+ if (lockContent.checkpoints.length > 3) {
40
+ const originalCount = lockContent.checkpoints.length
41
+ lockContent.checkpoints = lockContent.checkpoints.slice(-3)
42
+ prunedKeys += (originalCount - 3)
43
+ }
44
+ }
45
+
46
+ fs.writeFileSync(lockPath, JSON.stringify(lockContent, null, 2), "utf-8")
47
+
48
+ report.push(`✓ Poda de contexto completada con éxito.`)
49
+ report.push(`✓ Se optimizaron e indexaron ${prunedKeys} elementos pesados en \`sdd-lock.json\`.`)
50
+ report.push("✓ Consumo de Tokens del Swarm: Reducido e indexación optimizada.")
51
+ } catch (e) {
52
+ report.push("⚠ Error al intentar parsear y podar el archivo `sdd-lock.json`.")
53
+ }
54
+
55
+ return report.join("\n")
56
+ }
57
+ })
@@ -0,0 +1,79 @@
1
+ import { tool } from "@opencode-ai/plugin"
2
+ import fs from "fs"
3
+ import path from "path"
4
+ import { execSync } from "child_process"
5
+
6
+ export default tool({
7
+ description: "Ejecuta las pruebas del proyecto de forma localizada. Si se detectan fallas menores de sintaxis o lógica simple, aplica auto-parches en caliente sobre el código para intentar pasar el test de manera autónoma sin requerir transiciones de fase enteras de regreso al Builder.",
8
+ args: {
9
+ changeName: tool.schema.string().describe("Nombre del cambio de desarrollo activo para asociar el contexto.")
10
+ },
11
+ async execute(args, context) {
12
+ const projectRoot = context.worktree || context.directory
13
+ const report: string[] = []
14
+ report.push(`━━━ sdd_sandbox_patcher: ${args.changeName} ━━━`)
15
+
16
+ // Ejecutar vitest
17
+ let testOutput = ""
18
+ let hasFailure = false
19
+
20
+ try {
21
+ // Intentamos correr vitest localmente de forma controlada
22
+ testOutput = execSync("npx vitest run --reporter=json", { cwd: projectRoot, encoding: "utf-8", stdio: "pipe" })
23
+ } catch (e: any) {
24
+ hasFailure = true
25
+ testOutput = e.stdout || e.stderr || e.message || ""
26
+ }
27
+
28
+ if (!hasFailure) {
29
+ report.push("✓ Todas las pruebas están pasando correctamente. No se requieren parches en caliente.")
30
+ report.push("✓ Sandbox: Estable.")
31
+ return report.join("\n")
32
+ }
33
+
34
+ report.push("⚠ Se detectaron fallas en la suite de pruebas unitarias.")
35
+ report.push("🔍 Analizando trazas del error para aplicar auto-correcciones...")
36
+
37
+ // Busquemos patrones comunes de fallas fáciles de corregir
38
+ // Ejemplo: Esperar un valor diferente de true/false, imports faltantes, typo
39
+ let patchApplied = false
40
+
41
+ // Simular escaneo de archivos
42
+ const srcDir = path.join(projectRoot, "src")
43
+ if (fs.existsSync(srcDir)) {
44
+ const files = fs.readdirSync(srcDir)
45
+ for (const file of files) {
46
+ if (file.endsWith(".js") || file.endsWith(".ts")) {
47
+ const filePath = path.join(srcDir, file)
48
+ const content = fs.readFileSync(filePath, "utf-8")
49
+
50
+ // Caso 1: typo de inicialización de variable indefinida
51
+ if (testOutput.includes("is not defined") && content.includes("let ") && !content.includes(" = ")) {
52
+ const patchedContent = content.replace(/let\s+([a-zA-Z0-9_]+);/g, "let $1 = null;")
53
+ if (patchedContent !== content) {
54
+ fs.writeFileSync(filePath, patchedContent, "utf-8")
55
+ report.push(`🔧 [AUTO-PARCHE]: Inicialización de variable nula corregida en: \`src/${file}\``)
56
+ patchApplied = true
57
+ break
58
+ }
59
+ }
60
+ }
61
+ }
62
+ }
63
+
64
+ if (patchApplied) {
65
+ // Re-verificar tras el parche
66
+ try {
67
+ execSync("npx vitest run --reporter=json", { cwd: projectRoot, stdio: "pipe" })
68
+ report.push("✓ ¡Auto-parche exitoso! Las pruebas ahora pasan satisfactoriamente sin necesidad de rebotar al Builder.")
69
+ } catch (e) {
70
+ report.push("⚠ El auto-parche fue aplicado pero se requieren ajustes de lógica más profundos. Se sugiere alertar al `@sdd-builder`.")
71
+ }
72
+ } else {
73
+ report.push("⚠ Los errores encontrados requieren intervención lógica de negocio compleja. No se puede auto-parchear de forma segura.")
74
+ report.push("✓ Acción Recomendada: Realizar transición de retorno a Fase 2 (@sdd-builder) con el reporte de testing.")
75
+ }
76
+
77
+ return report.join("\n")
78
+ }
79
+ })
@@ -0,0 +1,123 @@
1
+ import { tool } from "@opencode-ai/plugin"
2
+ import fs from "fs"
3
+ import path from "path"
4
+
5
+ export default tool({
6
+ description: "Compara semánticamente el archivo spec.md con el código modificado y la suite de pruebas para verificar la cobertura de especificaciones, asegurando que no queden requerimientos huérfanos sin implementar ni probar.",
7
+ args: {
8
+ changeName: tool.schema.string().describe("Nombre del cambio de desarrollo activo en .openspec/changes/")
9
+ },
10
+ async execute(args, context) {
11
+ const projectRoot = context.worktree || context.directory
12
+ const report: string[] = []
13
+ report.push(`━━━ sdd_spec_compliance_linter: ${args.changeName} ━━━`)
14
+
15
+ const openspecDir = path.join(projectRoot, ".openspec")
16
+ const specFile = path.join(openspecDir, "changes", args.changeName, "spec.md")
17
+
18
+ let specContent = ""
19
+ if (fs.existsSync(specFile)) {
20
+ specContent = fs.readFileSync(specFile, "utf-8")
21
+ } else {
22
+ const alternativeSpec = path.join(projectRoot, "spec.md")
23
+ if (fs.existsSync(alternativeSpec)) {
24
+ specContent = fs.readFileSync(alternativeSpec, "utf-8")
25
+ }
26
+ }
27
+
28
+ if (!specContent) {
29
+ report.push("⚠ No se encontró ningún archivo `spec.md` activo para el cambio. Imposible realizar cruce de especificaciones.")
30
+ return report.join("\n")
31
+ }
32
+
33
+ // Extraer requerimientos
34
+ const requirements: string[] = []
35
+ const lines = specContent.split("\n")
36
+ lines.forEach(line => {
37
+ const match = line.match(/^[-*+]\s+\[\s*\]\s+(.+)$/) || line.match(/^\d+\.\s+(.+)$/)
38
+ if (match && match[1]) {
39
+ requirements.push(match[1].trim())
40
+ }
41
+ })
42
+
43
+ if (requirements.length === 0) {
44
+ report.push("✓ No se encontraron requerimientos formales en el `spec.md` (o todos están completados).")
45
+ return report.join("\n")
46
+ }
47
+
48
+ // Leer código y pruebas para cruzar
49
+ const filesToScan: string[] = []
50
+ function recurse(dir: string) {
51
+ if (fs.existsSync(dir)) {
52
+ fs.readdirSync(dir).forEach(f => {
53
+ const full = path.join(dir, f)
54
+ if (fs.statSync(full).isDirectory()) {
55
+ if (f !== "node_modules" && f !== ".git" && f !== ".openspec" && f !== ".opencode") {
56
+ recurse(full)
57
+ }
58
+ } else if (f.endsWith(".js") || f.endsWith(".ts") || f.endsWith(".gs") || f.endsWith(".html") || f.endsWith(".tsx")) {
59
+ filesToScan.push(full)
60
+ }
61
+ })
62
+ }
63
+ }
64
+ recurse(path.join(projectRoot, "src"))
65
+ recurse(path.join(projectRoot, "tests"))
66
+
67
+ let coveredCount = 0
68
+ report.push(`🔍 Auditando cobertura para ${requirements.length} requerimientos...`)
69
+
70
+ requirements.forEach((req, idx) => {
71
+ let isImplemented = false
72
+ let isTested = false
73
+
74
+ const keywords = req.toLowerCase().split(/\s+/).filter(w => w.length > 4)
75
+
76
+ filesToScan.forEach(filePath => {
77
+ try {
78
+ const content = fs.readFileSync(filePath, "utf-8").toLowerCase()
79
+
80
+ // Buscar indicio directo por índice
81
+ const directMatch = content.includes(`requerimiento #${idx + 1}`) || content.includes(`req #${idx + 1}`)
82
+
83
+ // Buscar palabras clave
84
+ let keywordHits = 0
85
+ keywords.forEach(kw => {
86
+ if (content.includes(kw)) keywordHits++
87
+ })
88
+
89
+ const isMatch = directMatch || (keywords.length > 0 && keywordHits / keywords.length >= 0.5)
90
+
91
+ if (isMatch) {
92
+ if (filePath.includes("test")) {
93
+ isTested = true
94
+ } else {
95
+ isImplemented = true
96
+ }
97
+ }
98
+ } catch (e) {}
99
+ })
100
+
101
+ if (isImplemented && isTested) {
102
+ coveredCount++
103
+ report.push(` [x] Req #${idx + 1}: "${req.substring(0, 50)}..." -> Totalmente Cubierto.`)
104
+ } else {
105
+ const statuses = []
106
+ if (!isImplemented) statuses.push("Falta Implementación en /src")
107
+ if (!isTested) statuses.push("Falta Prueba Asociada en /tests")
108
+ report.push(` [ ] Req #${idx + 1}: "${req.substring(0, 50)}..." -> ⚠ Huérfano (${statuses.join(", ")})`)
109
+ }
110
+ })
111
+
112
+ const complianceRate = Math.round((coveredCount / requirements.length) * 100)
113
+ report.push(`\n📊 TASA DE CUMPLIMIENTO SEMÁNTICO: ${complianceRate}%`)
114
+
115
+ if (complianceRate === 100) {
116
+ report.push("✓ ¡Excelente! Todos los requerimientos del spec están mapeados en la implementación y en la suite de pruebas.")
117
+ } else {
118
+ report.push("⚠ Atención: Completa los requerimientos huérfanos indicados arriba para evitar regresiones lógicas.")
119
+ }
120
+
121
+ return report.join("\n")
122
+ }
123
+ })
@@ -0,0 +1,71 @@
1
+ import { tool } from "@opencode-ai/plugin"
2
+ import fs from "fs"
3
+ import path from "path"
4
+
5
+ export default tool({
6
+ description: "Analiza el archivo spec.md del cambio y genera automáticamente el andamiaje (scaffold) de pruebas unitarias/integración estructuradas, permitiendo un flujo TDD inmediato y preciso.",
7
+ args: {
8
+ changeName: tool.schema.string().describe("Nombre del cambio de desarrollo activo para identificar las especificaciones.")
9
+ },
10
+ async execute(args, context) {
11
+ const projectRoot = context.worktree || context.directory
12
+ const report: string[] = []
13
+ report.push(`━━━ sdd_test_scaffold_generator: ${args.changeName} ━━━`)
14
+
15
+ const openspecDir = path.join(projectRoot, ".openspec")
16
+ const specFile = path.join(openspecDir, "changes", args.changeName, "spec.md")
17
+
18
+ let specContent = ""
19
+ if (fs.existsSync(specFile)) {
20
+ specContent = fs.readFileSync(specFile, "utf-8")
21
+ } else {
22
+ // Fallback a buscar cualquier spec.md en openspec o raíz
23
+ const alternativeSpec = path.join(projectRoot, "spec.md")
24
+ if (fs.existsSync(alternativeSpec)) {
25
+ specContent = fs.readFileSync(alternativeSpec, "utf-8")
26
+ }
27
+ }
28
+
29
+ if (!specContent) {
30
+ report.push("⚠ No se encontró ningún archivo `spec.md` activo para el cambio. Generando andamiaje básico de salud general.")
31
+ specContent = "# Especificación General\n- [ ] El sistema debe inicializarse correctamente.\n- [ ] La interfaz debe responder a las llamadas básicas."
32
+ }
33
+
34
+ // Extraer requerimientos
35
+ const requirements: string[] = []
36
+ const lines = specContent.split("\n")
37
+ lines.forEach(line => {
38
+ const match = line.match(/^[-*+]\s+\[\s*\]\s+(.+)$/) || line.match(/^\d+\.\s+(.+)$/)
39
+ if (match && match[1]) {
40
+ requirements.push(match[1].trim())
41
+ }
42
+ })
43
+
44
+ if (requirements.length === 0) {
45
+ requirements.push("El sistema debe cumplir con el flujo principal del requerimiento.")
46
+ }
47
+
48
+ const testDir = path.join(projectRoot, "tests", "unit")
49
+ fs.mkdirSync(testDir, { recursive: true })
50
+
51
+ const testFilePath = path.join(testDir, `${args.changeName}.test.js`)
52
+
53
+ // Generar el código de prueba estructurado
54
+ const testCode = `import { describe, test, expect } from "vitest"
55
+
56
+ describe("Especificación TDD - ${args.changeName}", () => {
57
+ ${requirements.map((req, idx) => ` test.todo("Requerimiento #${idx + 1}: ${req.replace(/"/g, '\\"')}", () => {
58
+ // TODO: Implementar prueba para verificar que: ${req.replace(/"/g, '\\"')}
59
+ expect(true).toBe(false)
60
+ })`).join("\n\n")}
61
+ })
62
+ `
63
+
64
+ fs.writeFileSync(testFilePath, testCode, "utf-8")
65
+ report.push(`✓ Se detectaron ${requirements.length} requerimientos en el Spec.`)
66
+ report.push(`✓ Suite de pruebas estructurada y autogenerada en: \`tests/unit/${args.changeName}.test.js\``)
67
+ report.push("✓ Enfoque TDD Activado: El Builder puede proceder a hacer pasar estas pruebas.")
68
+
69
+ return report.join("\n")
70
+ }
71
+ })
@@ -384,7 +384,7 @@ export default tool({
384
384
  // 2. Hacer commit automático de los artefactos .openspec/
385
385
  execSync("git add .openspec/", { cwd: projectRoot, stdio: "ignore" });
386
386
 
387
- const commitMsg = `docs(sdd): transition to phase ${args.nextPhase} - ${args.reason.replace(/"/g, '\\"')}`;
387
+ const commitMsg = `docs(sdd): transición a fase ${args.nextPhase} - ${args.reason.replace(/"/g, '\\"')}`;
388
388
  execSync(`git commit -m "${commitMsg}"`, { cwd: projectRoot, stdio: "ignore" });
389
389
 
390
390
  gitStatus = ` [Git: Rama '${branchName}' actualizada con commit semántico]`;