zugzbot-sdd 1.5.23 → 1.5.25

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.
@@ -0,0 +1,182 @@
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: "Audita estáticamente cualquier archivo (o conjunto de archivos modificados) para diagnosticar fallas de sintaxis, paréntesis/llaves rotas, errores sintácticos o problemas de formato en múltiples tecnologías (JS, TS, Python, JSON, HTML).",
7
+ args: {
8
+ filePath: tool.schema.string().optional().describe("Ruta absoluta o relativa del archivo a auditar de forma quirúrgica. Si se omite, detectará automáticamente los archivos modificados vía git.")
9
+ },
10
+ async execute(args, context) {
11
+ const projectRoot = context.worktree || context.directory;
12
+ const diagnostics = [];
13
+ let filesToAudit = [];
14
+ if (args.filePath) {
15
+ const absolutePath = path.isAbsolute(args.filePath) ? args.filePath : path.join(projectRoot, args.filePath);
16
+ if (fs.existsSync(absolutePath) && fs.statSync(absolutePath).isFile()) {
17
+ filesToAudit.push(absolutePath);
18
+ }
19
+ }
20
+ else {
21
+ // Autodetectar vía git status
22
+ try {
23
+ const gitStatus = execSync("git status --porcelain", { cwd: projectRoot, encoding: "utf-8" });
24
+ gitStatus.split("\n").forEach(line => {
25
+ const trimmed = line.trim();
26
+ if (trimmed) {
27
+ // git status --porcelain outputs e.g., "M src/index.js" or "A src/test.js"
28
+ const parts = trimmed.split(/\s+/);
29
+ if (parts.length >= 2) {
30
+ const relPath = parts.slice(1).join(" ");
31
+ const absolutePath = path.join(projectRoot, relPath);
32
+ if (fs.existsSync(absolutePath) && fs.statSync(absolutePath).isFile()) {
33
+ filesToAudit.push(absolutePath);
34
+ }
35
+ }
36
+ }
37
+ });
38
+ }
39
+ catch (e) { }
40
+ }
41
+ if (filesToAudit.length === 0) {
42
+ return JSON.stringify({
43
+ status: "APPROVED",
44
+ message: "✓ No se encontraron archivos para auditar o todo está limpio.",
45
+ diagnostics: []
46
+ }, null, 2);
47
+ }
48
+ for (const file of filesToAudit) {
49
+ const ext = path.extname(file);
50
+ const relFile = path.relative(projectRoot, file);
51
+ try {
52
+ const content = fs.readFileSync(file, "utf-8");
53
+ // 1. Validar JSON
54
+ if (ext === ".json") {
55
+ try {
56
+ JSON.parse(content);
57
+ }
58
+ catch (err) {
59
+ const lineMatch = err.message.match(/line (\d+)/i);
60
+ diagnostics.push({
61
+ file: relFile,
62
+ type: "JSON Syntax Error",
63
+ line: lineMatch ? parseInt(lineMatch[1]) : undefined,
64
+ message: err.message
65
+ });
66
+ }
67
+ }
68
+ // 2. Validar JavaScript / Google Apps Script
69
+ else if (ext === ".js" || ext === ".jsx" || ext === ".gs") {
70
+ try {
71
+ execSync(`node --check "${file}"`, { stdio: "pipe" });
72
+ }
73
+ catch (err) {
74
+ const stderr = err.stderr?.toString() || err.message || "";
75
+ // Parse line number from node check, e.g. "index.js:5" or "line 5"
76
+ const lineMatch = stderr.match(/:(\d+)\r?\n/m) || stderr.match(/line (\d+)/i);
77
+ diagnostics.push({
78
+ file: relFile,
79
+ type: "JavaScript Syntax Error",
80
+ line: lineMatch ? parseInt(lineMatch[1]) : undefined,
81
+ message: stderr.split("\n")[0] || "Syntax error detected"
82
+ });
83
+ }
84
+ }
85
+ // 3. Validar TypeScript
86
+ else if (ext === ".ts" || ext === ".tsx") {
87
+ try {
88
+ // Intentar un tsc --noEmit localizado o verificar balanceo de tokens
89
+ const hasBalancedParentheses = (str) => {
90
+ const stack = [];
91
+ const pairs = { ')': '(', '}': '{', ']': '[' };
92
+ for (let i = 0; i < str.length; i++) {
93
+ const char = str[i];
94
+ if (['(', '{', '['].includes(char)) {
95
+ stack.push(char);
96
+ }
97
+ else if ([')', '}', ']'].includes(char)) {
98
+ if (stack.pop() !== pairs[char])
99
+ return false;
100
+ }
101
+ }
102
+ return stack.length === 0;
103
+ };
104
+ if (!hasBalancedParentheses(content)) {
105
+ diagnostics.push({
106
+ file: relFile,
107
+ type: "TypeScript Token Mismatch",
108
+ message: "Falla de balanceo en paréntesis, llaves o corchetes detectada mediante auditoría estática."
109
+ });
110
+ }
111
+ }
112
+ catch (err) { }
113
+ }
114
+ // 4. Validar Python
115
+ else if (ext === ".py") {
116
+ try {
117
+ execSync(`python -m py_compile "${file}"`, { stdio: "pipe" });
118
+ }
119
+ catch (err) {
120
+ const stderr = err.stderr?.toString() || err.message || "";
121
+ const lineMatch = stderr.match(/line (\d+)/i);
122
+ diagnostics.push({
123
+ file: relFile,
124
+ type: "Python Syntax Error",
125
+ line: lineMatch ? parseInt(lineMatch[1]) : undefined,
126
+ message: stderr.split("\n")[0] || "Python syntax compilation failed"
127
+ });
128
+ }
129
+ }
130
+ // 5. Validar HTML
131
+ else if (ext === ".html") {
132
+ const unclosedTags = [];
133
+ const tags = content.match(/<[^>]+>/g) || [];
134
+ const stack = [];
135
+ tags.forEach(tag => {
136
+ const match = tag.match(/<\/?([a-zA-Z0-9:-]+)/);
137
+ if (match) {
138
+ const name = match[1];
139
+ if (tag.startsWith("</")) {
140
+ if (stack.length > 0 && stack[stack.length - 1] === name) {
141
+ stack.pop();
142
+ }
143
+ else {
144
+ unclosedTags.push(`Tag de cierre huérfano: ${tag}`);
145
+ }
146
+ }
147
+ else if (!tag.endsWith("/>") && !["img", "br", "hr", "input", "meta", "link"].includes(name)) {
148
+ stack.push(name);
149
+ }
150
+ }
151
+ });
152
+ if (stack.length > 0 || unclosedTags.length > 0) {
153
+ diagnostics.push({
154
+ file: relFile,
155
+ type: "HTML Tag Mismatch",
156
+ message: `Estructura HTML mal balanceada. Tags sin cerrar: ${stack.join(", ")}. ${unclosedTags.join("; ")}`
157
+ });
158
+ }
159
+ }
160
+ }
161
+ catch (e) {
162
+ diagnostics.push({
163
+ file: relFile,
164
+ type: "Read Error",
165
+ message: `Imposible leer o analizar el archivo: ${e.message}`
166
+ });
167
+ }
168
+ }
169
+ if (diagnostics.length > 0) {
170
+ return JSON.stringify({
171
+ status: "FAILED",
172
+ message: `❌ VALIDACIÓN DE SINTAXIS FALLIDA: Se detectaron ${diagnostics.length} errores de sintaxis o balanceo en los archivos modificados.`,
173
+ diagnostics
174
+ }, null, 2);
175
+ }
176
+ return JSON.stringify({
177
+ status: "APPROVED",
178
+ message: "✓ Validación de sintaxis exitosa. Todos los archivos auditados se encuentran 100% íntegros y limpios.",
179
+ diagnostics: []
180
+ }, null, 2);
181
+ }
182
+ });
@@ -16,6 +16,7 @@ permission:
16
16
  "sdd_visual_regression_diff": allow
17
17
  "sdd_auto_api_mocker": allow
18
18
  "sdd_spec_compliance_linter": allow
19
+ "sdd_syntax_and_linter_auditor": allow
19
20
  ---
20
21
 
21
22
  # @sdd-builder
@@ -27,6 +28,7 @@ permission:
27
28
  ## DO
28
29
  - Implementa los cambios en el código según el spec, asegurándote de revisar `.openspec/brain.md` para cumplir estrictamente con los patrones técnicos exitosos y evitar reintroducir malas prácticas.
29
30
  - Usa `edit` para parches quirúrgicos (prohibido reescribir archivos completos).
31
+ - **Pre-chequeo Local de Sintaxis**: Ejecuta OBLIGATORIAMENTE la herramienta premium `sdd_syntax_and_linter_auditor` sobre tus archivos modificados antes de finalizar para certificar la total integridad sintáctica de tus cambios (erradicando paréntesis, corchetes o llaves abiertas, y problemas de balanceo en general).
30
32
  - **Escaneo SAST Quirúrgico**: Ejecuta `sdd_security_vulnerability_scanner` sobre tus archivos modificados antes de cerrar tu implementación.
31
33
  - **Validación de Secretos**: Corre `sdd_secret_scanner` para asegurarte de no dejar tokens/passwords temporales de desarrollo.
32
34
  - **Linter de Especificación**: Ejecuta `sdd_spec_compliance_linter` para certificar que todos los criterios de aceptación del Spec estén cubiertos.
@@ -50,7 +52,7 @@ permission:
50
52
  - ❌ Escribir o autogenerar suites de tests unitarios o de integración
51
53
  - ❌ Ejecutar validación de linter o auditorías UI por cuenta propia (delegar a `@sdd-tester`)
52
54
  - ❌ Realizar deploys, pushes, o publicaciones de ningún tipo
53
- - ❌ Usar herramientas que no le fueron asignadas (`sdd_transition`, `sdd_ui_auditor`, `sdd_secret_scanner`, `sdd_security_vulnerability_scanner`, `sdd_visual_regression_diff`, `sdd_auto_api_mocker`, `sdd_spec_compliance_linter`)
55
+ - ❌ Usar herramientas que no le fueron asignadas (`sdd_transition`, `sdd_ui_auditor`, `sdd_secret_scanner`, `sdd_security_vulnerability_scanner`, `sdd_visual_regression_diff`, `sdd_auto_api_mocker`, `sdd_spec_compliance_linter`, `sdd_syntax_and_linter_auditor`)
54
56
  - ❌ Modificar `package.json`, `tsconfig.json`, o archivos de configuración de proyecto
55
57
  - ❌ Ignorar el spec.md — toda implementación debe trackear contra los criterios de aceptación del spec
56
58
 
@@ -22,6 +22,7 @@ permission:
22
22
  "sdd_test_scaffold_generator": allow
23
23
  "sdd_spec_compliance_linter": allow
24
24
  "sdd_sandbox_patcher": allow
25
+ "sdd_syntax_and_linter_auditor": allow
25
26
  ---
26
27
 
27
28
  # @sdd-tester
@@ -34,15 +35,20 @@ permission:
34
35
  ## DO
35
36
  1. **Detección de Regresiones Históricas**: Lee `.openspec/brain.md` para identificar qué fallas específicas o comportamientos errados ocurrieron en el pasado. Debes verificar de manera explícita y prioritaria que la nueva implementación **no reintroduzca ninguno de los bugs o comportamientos incorrectos registrados en el Cerebro**.
36
37
  2. **Identificar Ecosistema Tecnológico**: Inspecciona la raíz del codebase (archivos como `package.json`, `requirements.txt`, `pyproject.toml`, `platformio.ini`, `CMakeLists.txt`, `go.mod`, etc.) para detectar el stack técnico del proyecto.
37
- 3. **Validación y Auditorías**:
38
+ 3. **Configuración Proactiva de Linter/Calidad**:
39
+ - Si el proyecto no cuenta con una configuración mínima de linter o validador de sintaxis estática (como `.eslintrc`, `tsconfig.json` o similar) o carece de dependencias básicas de validación, **DEBES configurar proactivamente** los archivos iniciales mínimos o instalar localmente los paquetes de desarrollo requeridos (`npm install --save-dev eslint` o configuraciones nativas ligeras) para asegurar que el entorno sea capaz de diagnosticar la calidad del código.
40
+ 4. **Chequeo Obligatorio de Sintaxis y Compilación (Por Archivo Modificado)**:
41
+ - Identifica todos los archivos modificados en la sesión. Para realizar un diagnóstico completo y universal de sintaxis, **DEBES ejecutar OBLIGATORIAMENTE la herramienta premium `sdd_syntax_and_linter_auditor`** sobre cada archivo modificado o para el conjunto completo de cambios.
42
+ - Si la herramienta reporta un estado `FAILED` con errores de sintaxis (paréntesis, llaves o corchetes rotos, archivos JSON mal formateados, tags HTML sin balancear, etc.), **BLOQUEA de inmediato la transición** (marcando success/blocked a `blocked`), a menos que sea un error simple corregible usando `sdd_sandbox_patcher`.
43
+ 5. **Validación y Auditorías del Swarm**:
38
44
  - Ejecuta `sdd_spec_compliance_linter` para cruzar requerimientos.
39
45
  - Ejecuta `sdd_security_vulnerability_scanner` para detectar vulnerabilidades en el código.
40
46
  - Corre `sdd_visual_regression_diff` para auditar la interfaz y estilos.
41
47
  - Ejecuta `sdd_performance_regress_profiler` para medir latencias y rendimiento.
42
48
  - Usa `sdd_diff_impact_analyzer` para calcular el radio de impacto final.
43
- 4. **Ejecutar Suite de Pruebas**: Corre la suite de tests nativos del proyecto (ej: `npm run test` / `npx vitest run`).
44
- 5. **Autocorrección con Patcher**: Si detectas fallos unitarios simples, invoca `sdd_sandbox_patcher` para aplicar correcciones automáticas inmediatas sin retroceder de fase.
45
- 6. **Validación UI**: Ejecuta `sdd_ui_auditor` si el proyecto es una app web/frontend con visualización o HTML.
49
+ 6. **Ejecutar Suite de Pruebas**: Corre la suite de tests nativos del proyecto (ej: `npm run test` / `npx vitest run`).
50
+ 7. **Autocorrección con Patcher**: Si detectas fallos unitarios simples o de sintaxis menores, invoca `sdd_sandbox_patcher` para aplicar correcciones automáticas inmediatas sin retroceder de fase.
51
+ 8. **Validación UI**: Ejecuta `sdd_ui_auditor` si el proyecto es una app web/frontend con visualización o HTML.
46
52
 
47
53
  ## WRITE
48
54
  - `.openspec/changes/<change-name>/validation_report.md`
@@ -84,7 +90,7 @@ permission:
84
90
  - ❌ Reescribir archivos de código — solo autocorregir errores de sintaxis simples usando `sdd_sandbox_patcher`
85
91
  - ❌ Escribir tests unitarios o de integración nuevos
86
92
  - ❌ Modificar archivos fuera de `.openspec/changes/<change-name>/`
87
- - ❌ Usar herramientas que no le fueron asignadas (`sdd_transition`, `sdd_ui_auditor`, `sdd_spec_validator`, `sdd_regression_detector`, `sdd_bdd_tester`, `sdd_requirement_tracker`, `sdd_diff_impact_analyzer`, `sdd_security_vulnerability_scanner`, `sdd_visual_regression_diff`, `sdd_performance_regress_profiler`, `sdd_auto_api_mocker`, `sdd_test_scaffold_generator`, `sdd_spec_compliance_linter`, `sdd_sandbox_patcher`)
93
+ - ❌ Usar herramientas que no le fueron asignadas (`sdd_transition`, `sdd_ui_auditor`, `sdd_spec_validator`, `sdd_regression_detector`, `sdd_bdd_tester`, `sdd_requirement_tracker`, `sdd_diff_impact_analyzer`, `sdd_security_vulnerability_scanner`, `sdd_visual_regression_diff`, `sdd_performance_regress_profiler`, `sdd_auto_api_mocker`, `sdd_test_scaffold_generator`, `sdd_spec_compliance_linter`, `sdd_sandbox_patcher`, `sdd_syntax_and_linter_auditor`)
88
94
 
89
95
  > [!IMPORTANT]
90
96
  > SÓLO DEBE hacer: ejecutar linter, auditorías UI, validaciones estáticas, generar `validation_report.md`, invocar `sdd_transition` al completar.
package/bin/zugzbot.js CHANGED
@@ -137,7 +137,8 @@ function buildOpencodeJson(models) {
137
137
  "sdd_security_vulnerability_scanner": "allow",
138
138
  "sdd_visual_regression_diff": "allow",
139
139
  "sdd_auto_api_mocker": "allow",
140
- "sdd_spec_compliance_linter": "allow"
140
+ "sdd_spec_compliance_linter": "allow",
141
+ "sdd_syntax_and_linter_auditor": "allow"
141
142
  }
142
143
  }
143
144
  },
@@ -163,7 +164,8 @@ function buildOpencodeJson(models) {
163
164
  "sdd_auto_api_mocker": "allow",
164
165
  "sdd_test_scaffold_generator": "allow",
165
166
  "sdd_spec_compliance_linter": "allow",
166
- "sdd_sandbox_patcher": "allow"
167
+ "sdd_sandbox_patcher": "allow",
168
+ "sdd_syntax_and_linter_auditor": "allow"
167
169
  }
168
170
  }
169
171
  },
package/opencode.json CHANGED
@@ -74,7 +74,8 @@
74
74
  "sdd_security_vulnerability_scanner": "allow",
75
75
  "sdd_visual_regression_diff": "allow",
76
76
  "sdd_auto_api_mocker": "allow",
77
- "sdd_spec_compliance_linter": "allow"
77
+ "sdd_spec_compliance_linter": "allow",
78
+ "sdd_syntax_and_linter_auditor": "allow"
78
79
  }
79
80
  }
80
81
  },
@@ -100,7 +101,8 @@
100
101
  "sdd_auto_api_mocker": "allow",
101
102
  "sdd_test_scaffold_generator": "allow",
102
103
  "sdd_spec_compliance_linter": "allow",
103
- "sdd_sandbox_patcher": "allow"
104
+ "sdd_sandbox_patcher": "allow",
105
+ "sdd_syntax_and_linter_auditor": "allow"
104
106
  }
105
107
  }
106
108
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zugzbot-sdd",
3
- "version": "1.5.23",
3
+ "version": "1.5.25",
4
4
  "description": "Zugzbot SDD Swarm - Spec-Driven Development Harness for OpenCode",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -0,0 +1,187 @@
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: "Audita estáticamente cualquier archivo (o conjunto de archivos modificados) para diagnosticar fallas de sintaxis, paréntesis/llaves rotas, errores sintácticos o problemas de formato en múltiples tecnologías (JS, TS, Python, JSON, HTML).",
8
+ args: {
9
+ filePath: tool.schema.string().optional().describe("Ruta absoluta o relativa del archivo a auditar de forma quirúrgica. Si se omite, detectará automáticamente los archivos modificados vía git.")
10
+ },
11
+ async execute(args, context) {
12
+ const projectRoot = context.worktree || context.directory
13
+ const diagnostics: Array<{ file: string; type: string; line?: number; message: string }> = []
14
+
15
+ let filesToAudit: string[] = []
16
+
17
+ if (args.filePath) {
18
+ const absolutePath = path.isAbsolute(args.filePath) ? args.filePath : path.join(projectRoot, args.filePath)
19
+ if (fs.existsSync(absolutePath) && fs.statSync(absolutePath).isFile()) {
20
+ filesToAudit.push(absolutePath)
21
+ }
22
+ } else {
23
+ // Autodetectar vía git status
24
+ try {
25
+ const gitStatus = execSync("git status --porcelain", { cwd: projectRoot, encoding: "utf-8" })
26
+ gitStatus.split("\n").forEach(line => {
27
+ const trimmed = line.trim()
28
+ if (trimmed) {
29
+ // git status --porcelain outputs e.g., "M src/index.js" or "A src/test.js"
30
+ const parts = trimmed.split(/\s+/)
31
+ if (parts.length >= 2) {
32
+ const relPath = parts.slice(1).join(" ")
33
+ const absolutePath = path.join(projectRoot, relPath)
34
+ if (fs.existsSync(absolutePath) && fs.statSync(absolutePath).isFile()) {
35
+ filesToAudit.push(absolutePath)
36
+ }
37
+ }
38
+ }
39
+ })
40
+ } catch (e) {}
41
+ }
42
+
43
+ if (filesToAudit.length === 0) {
44
+ return JSON.stringify({
45
+ status: "APPROVED",
46
+ message: "✓ No se encontraron archivos para auditar o todo está limpio.",
47
+ diagnostics: []
48
+ }, null, 2)
49
+ }
50
+
51
+ for (const file of filesToAudit) {
52
+ const ext = path.extname(file)
53
+ const relFile = path.relative(projectRoot, file)
54
+
55
+ try {
56
+ const content = fs.readFileSync(file, "utf-8")
57
+
58
+ // 1. Validar JSON
59
+ if (ext === ".json") {
60
+ try {
61
+ JSON.parse(content)
62
+ } catch (err: any) {
63
+ const lineMatch = err.message.match(/line (\d+)/i)
64
+ diagnostics.push({
65
+ file: relFile,
66
+ type: "JSON Syntax Error",
67
+ line: lineMatch ? parseInt(lineMatch[1]) : undefined,
68
+ message: err.message
69
+ })
70
+ }
71
+ }
72
+
73
+ // 2. Validar JavaScript / Google Apps Script
74
+ else if (ext === ".js" || ext === ".jsx" || ext === ".gs") {
75
+ try {
76
+ execSync(`node --check "${file}"`, { stdio: "pipe" })
77
+ } catch (err: any) {
78
+ const stderr = err.stderr?.toString() || err.message || ""
79
+ // Parse line number from node check, e.g. "index.js:5" or "line 5"
80
+ const lineMatch = stderr.match(/:(\d+)\r?\n/m) || stderr.match(/line (\d+)/i)
81
+ diagnostics.push({
82
+ file: relFile,
83
+ type: "JavaScript Syntax Error",
84
+ line: lineMatch ? parseInt(lineMatch[1]) : undefined,
85
+ message: stderr.split("\n")[0] || "Syntax error detected"
86
+ })
87
+ }
88
+ }
89
+
90
+ // 3. Validar TypeScript
91
+ else if (ext === ".ts" || ext === ".tsx") {
92
+ try {
93
+ // Intentar un tsc --noEmit localizado o verificar balanceo de tokens
94
+ const hasBalancedParentheses = (str: string) => {
95
+ const stack: string[] = []
96
+ const pairs: Record<string, string> = { ')': '(', '}': '{', ']': '[' }
97
+ for (let i = 0; i < str.length; i++) {
98
+ const char = str[i]
99
+ if (['(', '{', '['].includes(char)) {
100
+ stack.push(char)
101
+ } else if ([')', '}', ']'].includes(char)) {
102
+ if (stack.pop() !== pairs[char]) return false
103
+ }
104
+ }
105
+ return stack.length === 0
106
+ }
107
+ if (!hasBalancedParentheses(content)) {
108
+ diagnostics.push({
109
+ file: relFile,
110
+ type: "TypeScript Token Mismatch",
111
+ message: "Falla de balanceo en paréntesis, llaves o corchetes detectada mediante auditoría estática."
112
+ })
113
+ }
114
+ } catch (err: any) {}
115
+ }
116
+
117
+ // 4. Validar Python
118
+ else if (ext === ".py") {
119
+ try {
120
+ execSync(`python -m py_compile "${file}"`, { stdio: "pipe" })
121
+ } catch (err: any) {
122
+ const stderr = err.stderr?.toString() || err.message || ""
123
+ const lineMatch = stderr.match(/line (\d+)/i)
124
+ diagnostics.push({
125
+ file: relFile,
126
+ type: "Python Syntax Error",
127
+ line: lineMatch ? parseInt(lineMatch[1]) : undefined,
128
+ message: stderr.split("\n")[0] || "Python syntax compilation failed"
129
+ })
130
+ }
131
+ }
132
+
133
+ // 5. Validar HTML
134
+ else if (ext === ".html") {
135
+ const unclosedTags: string[] = []
136
+ const tags = content.match(/<[^>]+>/g) || []
137
+ const stack: string[] = []
138
+
139
+ tags.forEach(tag => {
140
+ const match = tag.match(/<\/?([a-zA-Z0-9:-]+)/)
141
+ if (match) {
142
+ const name = match[1]
143
+ if (tag.startsWith("</")) {
144
+ if (stack.length > 0 && stack[stack.length - 1] === name) {
145
+ stack.pop()
146
+ } else {
147
+ unclosedTags.push(`Tag de cierre huérfano: ${tag}`)
148
+ }
149
+ } else if (!tag.endsWith("/>") && !["img", "br", "hr", "input", "meta", "link"].includes(name)) {
150
+ stack.push(name)
151
+ }
152
+ }
153
+ })
154
+
155
+ if (stack.length > 0 || unclosedTags.length > 0) {
156
+ diagnostics.push({
157
+ file: relFile,
158
+ type: "HTML Tag Mismatch",
159
+ message: `Estructura HTML mal balanceada. Tags sin cerrar: ${stack.join(", ")}. ${unclosedTags.join("; ")}`
160
+ })
161
+ }
162
+ }
163
+
164
+ } catch (e: any) {
165
+ diagnostics.push({
166
+ file: relFile,
167
+ type: "Read Error",
168
+ message: `Imposible leer o analizar el archivo: ${e.message}`
169
+ })
170
+ }
171
+ }
172
+
173
+ if (diagnostics.length > 0) {
174
+ return JSON.stringify({
175
+ status: "FAILED",
176
+ message: `❌ VALIDACIÓN DE SINTAXIS FALLIDA: Se detectaron ${diagnostics.length} errores de sintaxis o balanceo en los archivos modificados.`,
177
+ diagnostics
178
+ }, null, 2)
179
+ }
180
+
181
+ return JSON.stringify({
182
+ status: "APPROVED",
183
+ message: "✓ Validación de sintaxis exitosa. Todos los archivos auditados se encuentran 100% íntegros y limpios.",
184
+ diagnostics: []
185
+ }, null, 2)
186
+ }
187
+ })