zugzbot-sdd 1.5.24 → 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
|
+
});
|
package/agents/sdd-builder.md
CHANGED
|
@@ -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,7 +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).
|
|
30
|
-
- **Pre-chequeo Local de Sintaxis**:
|
|
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).
|
|
31
32
|
- **Escaneo SAST Quirúrgico**: Ejecuta `sdd_security_vulnerability_scanner` sobre tus archivos modificados antes de cerrar tu implementación.
|
|
32
33
|
- **Validación de Secretos**: Corre `sdd_secret_scanner` para asegurarte de no dejar tokens/passwords temporales de desarrollo.
|
|
33
34
|
- **Linter de Especificación**: Ejecuta `sdd_spec_compliance_linter` para certificar que todos los criterios de aceptación del Spec estén cubiertos.
|
|
@@ -51,7 +52,7 @@ permission:
|
|
|
51
52
|
- ❌ Escribir o autogenerar suites de tests unitarios o de integración
|
|
52
53
|
- ❌ Ejecutar validación de linter o auditorías UI por cuenta propia (delegar a `@sdd-tester`)
|
|
53
54
|
- ❌ Realizar deploys, pushes, o publicaciones de ningún tipo
|
|
54
|
-
- ❌ 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`)
|
|
55
56
|
- ❌ Modificar `package.json`, `tsconfig.json`, o archivos de configuración de proyecto
|
|
56
57
|
- ❌ Ignorar el spec.md — toda implementación debe trackear contra los criterios de aceptación del spec
|
|
57
58
|
|
package/agents/sdd-tester.md
CHANGED
|
@@ -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
|
|
@@ -37,13 +38,8 @@ permission:
|
|
|
37
38
|
3. **Configuración Proactiva de Linter/Calidad**:
|
|
38
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.
|
|
39
40
|
4. **Chequeo Obligatorio de Sintaxis y Compilación (Por Archivo Modificado)**:
|
|
40
|
-
- Identifica todos los archivos modificados en la sesión. Para
|
|
41
|
-
|
|
42
|
-
- **TypeScript (`.ts`, `.tsx`)**: Ejecutar `npx tsc --noEmit` o el compilador local aplicable para auditar tipos e integridad sintáctica.
|
|
43
|
-
- **Python (`.py`)**: Ejecuta `python -m py_compile <ruta-del-archivo>`.
|
|
44
|
-
- **JSON (`.json`)**: Ejecuta una verificación estructural sintáctica (ej: `node -e "JSON.parse(fs.readFileSync('<ruta-del-archivo>'))"`).
|
|
45
|
-
- **HTML (`.html`)**: Realiza análisis estructural o de balanceo de tags (ej. usando las herramientas de test locales).
|
|
46
|
-
- Si algún archivo modificado reporta un error de sintaxis o compilación, **BLOQUEA de inmediato la transición** (marcando success/blocked a `blocked`), a menos que sea un error simple corregible usando `sdd_sandbox_patcher`.
|
|
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`.
|
|
47
43
|
5. **Validación y Auditorías del Swarm**:
|
|
48
44
|
- Ejecuta `sdd_spec_compliance_linter` para cruzar requerimientos.
|
|
49
45
|
- Ejecuta `sdd_security_vulnerability_scanner` para detectar vulnerabilidades en el código.
|
|
@@ -94,7 +90,7 @@ permission:
|
|
|
94
90
|
- ❌ Reescribir archivos de código — solo autocorregir errores de sintaxis simples usando `sdd_sandbox_patcher`
|
|
95
91
|
- ❌ Escribir tests unitarios o de integración nuevos
|
|
96
92
|
- ❌ Modificar archivos fuera de `.openspec/changes/<change-name>/`
|
|
97
|
-
- ❌ 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`)
|
|
98
94
|
|
|
99
95
|
> [!IMPORTANT]
|
|
100
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
|
@@ -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
|
+
})
|