zugzbot-sdd 1.5.25 → 1.5.26

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.
@@ -3,7 +3,7 @@ import fs from "fs";
3
3
  import path from "path";
4
4
  import { execSync } from "child_process";
5
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).",
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, GS, CSS, CPP, SH, YAML, PHP, MD).",
7
7
  args: {
8
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
9
  },
@@ -24,7 +24,6 @@ export default tool({
24
24
  gitStatus.split("\n").forEach(line => {
25
25
  const trimmed = line.trim();
26
26
  if (trimmed) {
27
- // git status --porcelain outputs e.g., "M src/index.js" or "A src/test.js"
28
27
  const parts = trimmed.split(/\s+/);
29
28
  if (parts.length >= 2) {
30
29
  const relPath = parts.slice(1).join(" ");
@@ -45,12 +44,45 @@ export default tool({
45
44
  diagnostics: []
46
45
  }, null, 2);
47
46
  }
47
+ // Heurística de balanceo de paréntesis/llaves/corchetes general
48
+ const hasBalancedTokens = (str) => {
49
+ const stack = [];
50
+ const pairs = { ')': '(', '}': '{', ']': '[' };
51
+ let lineNum = 1;
52
+ for (let i = 0; i < str.length; i++) {
53
+ const char = str[i];
54
+ if (char === '\n') {
55
+ lineNum++;
56
+ continue;
57
+ }
58
+ if (['(', '{', '['].includes(char)) {
59
+ stack.push({ char, index: i, line: lineNum });
60
+ }
61
+ else if ([')', '}', ']'].includes(char)) {
62
+ const top = stack.pop();
63
+ if (!top || top.char !== pairs[char]) {
64
+ return {
65
+ balanced: false,
66
+ message: `Error de balanceo: Se encontró '${char}' en línea ${lineNum} pero se esperaba cierre para '${top ? top.char : 'ninguno'}' (línea ${top ? top.line : 'N/A'}).`
67
+ };
68
+ }
69
+ }
70
+ }
71
+ if (stack.length > 0) {
72
+ const unclosed = stack.pop();
73
+ return {
74
+ balanced: false,
75
+ message: `Error de balanceo: El token '${unclosed.char}' abierto en la línea ${unclosed.line} nunca fue cerrado.`
76
+ };
77
+ }
78
+ return { balanced: true };
79
+ };
48
80
  for (const file of filesToAudit) {
49
- const ext = path.extname(file);
81
+ const ext = path.extname(file).toLowerCase();
50
82
  const relFile = path.relative(projectRoot, file);
51
83
  try {
52
84
  const content = fs.readFileSync(file, "utf-8");
53
- // 1. Validar JSON
85
+ // 1. JSON
54
86
  if (ext === ".json") {
55
87
  try {
56
88
  JSON.parse(content);
@@ -65,53 +97,34 @@ export default tool({
65
97
  });
66
98
  }
67
99
  }
68
- // 2. Validar JavaScript / Google Apps Script
100
+ // 2. JavaScript / Google Apps Script (.js, .jsx, .gs)
69
101
  else if (ext === ".js" || ext === ".jsx" || ext === ".gs") {
70
102
  try {
71
103
  execSync(`node --check "${file}"`, { stdio: "pipe" });
72
104
  }
73
105
  catch (err) {
74
106
  const stderr = err.stderr?.toString() || err.message || "";
75
- // Parse line number from node check, e.g. "index.js:5" or "line 5"
76
107
  const lineMatch = stderr.match(/:(\d+)\r?\n/m) || stderr.match(/line (\d+)/i);
77
108
  diagnostics.push({
78
109
  file: relFile,
79
- type: "JavaScript Syntax Error",
110
+ type: "JS/GS Syntax Error",
80
111
  line: lineMatch ? parseInt(lineMatch[1]) : undefined,
81
112
  message: stderr.split("\n")[0] || "Syntax error detected"
82
113
  });
83
114
  }
84
115
  }
85
- // 3. Validar TypeScript
116
+ // 3. TypeScript / TSX
86
117
  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
- }
118
+ const balanceCheck = hasBalancedTokens(content);
119
+ if (!balanceCheck.balanced) {
120
+ diagnostics.push({
121
+ file: relFile,
122
+ type: "TypeScript Syntax/Token Mismatch",
123
+ message: balanceCheck.message || "Falla de balanceo en tokens estructurados."
124
+ });
111
125
  }
112
- catch (err) { }
113
126
  }
114
- // 4. Validar Python
127
+ // 4. Python (.py)
115
128
  else if (ext === ".py") {
116
129
  try {
117
130
  execSync(`python -m py_compile "${file}"`, { stdio: "pipe" });
@@ -127,7 +140,7 @@ export default tool({
127
140
  });
128
141
  }
129
142
  }
130
- // 5. Validar HTML
143
+ // 5. HTML
131
144
  else if (ext === ".html") {
132
145
  const unclosedTags = [];
133
146
  const tags = content.match(/<[^>]+>/g) || [];
@@ -157,11 +170,92 @@ export default tool({
157
170
  });
158
171
  }
159
172
  }
173
+ // 6. Shell Scripts (.sh, .bash)
174
+ else if (ext === ".sh" || ext === ".bash") {
175
+ try {
176
+ execSync(`bash -n "${file}"`, { stdio: "pipe" });
177
+ }
178
+ catch (err) {
179
+ const stderr = err.stderr?.toString() || err.message || "";
180
+ const lineMatch = stderr.match(/: line (\d+):/i) || stderr.match(/:(\d+):/i);
181
+ diagnostics.push({
182
+ file: relFile,
183
+ type: "Shell Script Syntax Error",
184
+ line: lineMatch ? parseInt(lineMatch[1]) : undefined,
185
+ message: stderr.split("\n")[0] || "Shell script syntax check failed"
186
+ });
187
+ }
188
+ }
189
+ // 7. CSS
190
+ else if (ext === ".css") {
191
+ const balanceCheck = hasBalancedTokens(content);
192
+ if (!balanceCheck.balanced) {
193
+ diagnostics.push({
194
+ file: relFile,
195
+ type: "CSS Rule Mismatch",
196
+ message: `Falla de balanceo en CSS: ${balanceCheck.message}`
197
+ });
198
+ }
199
+ }
200
+ // 8. C / C++ (.c, .cpp, .h, .hpp)
201
+ else if (ext === ".c" || ext === ".cpp" || ext === ".h" || ext === ".hpp") {
202
+ const balanceCheck = hasBalancedTokens(content);
203
+ if (!balanceCheck.balanced) {
204
+ diagnostics.push({
205
+ file: relFile,
206
+ type: "C/C++ Syntax Token Mismatch",
207
+ message: `Falla estructural C/C++: ${balanceCheck.message}`
208
+ });
209
+ }
210
+ }
211
+ // 9. YAML / YML
212
+ else if (ext === ".yaml" || ext === ".yml") {
213
+ // Chequeo básico de indentación y formato YAML sin requerir librerías externas pesadas
214
+ const lines = content.split("\n");
215
+ lines.forEach((line, idx) => {
216
+ const spaces = line.match(/^(\s*)/)?.[1].length || 0;
217
+ if (spaces % 2 !== 0 && line.trim().length > 0 && !line.trim().startsWith("#")) {
218
+ diagnostics.push({
219
+ file: relFile,
220
+ type: "YAML Indentation Warning",
221
+ line: idx + 1,
222
+ message: `Indentación sospechosa (${spaces} espacios). YAML prefiere múltiplos de 2.`
223
+ });
224
+ }
225
+ });
226
+ }
227
+ // 10. PHP
228
+ else if (ext === ".php") {
229
+ try {
230
+ execSync(`php -l "${file}"`, { stdio: "pipe" });
231
+ }
232
+ catch (err) {
233
+ const stderr = err.stderr?.toString() || err.message || "";
234
+ const lineMatch = stderr.match(/on line (\d+)/i);
235
+ diagnostics.push({
236
+ file: relFile,
237
+ type: "PHP Syntax Error",
238
+ line: lineMatch ? parseInt(lineMatch[1]) : undefined,
239
+ message: stderr.split("\n")[0] || "PHP syntax check failed"
240
+ });
241
+ }
242
+ }
243
+ // 11. Markdown (.md)
244
+ else if (ext === ".md") {
245
+ const openBlocks = (content.match(/^```/gm) || []).length;
246
+ if (openBlocks % 2 !== 0) {
247
+ diagnostics.push({
248
+ file: relFile,
249
+ type: "Markdown Fenced Block Mismatch",
250
+ message: "Se detectó un bloque de código fenced (```) sin cerrar en el archivo markdown."
251
+ });
252
+ }
253
+ }
160
254
  }
161
255
  catch (e) {
162
256
  diagnostics.push({
163
257
  file: relFile,
164
- type: "Read Error",
258
+ type: "Read/Compile Error",
165
259
  message: `Imposible leer o analizar el archivo: ${e.message}`
166
260
  });
167
261
  }
@@ -169,7 +263,7 @@ export default tool({
169
263
  if (diagnostics.length > 0) {
170
264
  return JSON.stringify({
171
265
  status: "FAILED",
172
- message: `❌ VALIDACIÓN DE SINTAXIS FALLIDA: Se detectaron ${diagnostics.length} errores de sintaxis o balanceo en los archivos modificados.`,
266
+ message: `❌ VALIDACIÓN DE SINTAXIS MULTI-TECNOLOGÍA FALLIDA: Se detectaron ${diagnostics.length} errores estáticos o de formato en los archivos modificados.`,
173
267
  diagnostics
174
268
  }, null, 2);
175
269
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zugzbot-sdd",
3
- "version": "1.5.25",
3
+ "version": "1.5.26",
4
4
  "description": "Zugzbot SDD Swarm - Spec-Driven Development Harness for OpenCode",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -4,7 +4,7 @@ import path from "path"
4
4
  import { execSync } from "child_process"
5
5
 
6
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).",
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, GS, CSS, CPP, SH, YAML, PHP, MD).",
8
8
  args: {
9
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
10
  },
@@ -26,7 +26,6 @@ export default tool({
26
26
  gitStatus.split("\n").forEach(line => {
27
27
  const trimmed = line.trim()
28
28
  if (trimmed) {
29
- // git status --porcelain outputs e.g., "M src/index.js" or "A src/test.js"
30
29
  const parts = trimmed.split(/\s+/)
31
30
  if (parts.length >= 2) {
32
31
  const relPath = parts.slice(1).join(" ")
@@ -48,14 +47,51 @@ export default tool({
48
47
  }, null, 2)
49
48
  }
50
49
 
50
+ // Heurística de balanceo de paréntesis/llaves/corchetes general
51
+ const hasBalancedTokens = (str: string): { balanced: boolean; message?: string } => {
52
+ const stack: { char: string; index: number; line: number }[] = []
53
+ const pairs: Record<string, string> = { ')': '(', '}': '{', ']': '[' }
54
+ let lineNum = 1
55
+
56
+ for (let i = 0; i < str.length; i++) {
57
+ const char = str[i]
58
+ if (char === '\n') {
59
+ lineNum++
60
+ continue
61
+ }
62
+
63
+ if (['(', '{', '['].includes(char)) {
64
+ stack.push({ char, index: i, line: lineNum })
65
+ } else if ([')', '}', ']'].includes(char)) {
66
+ const top = stack.pop()
67
+ if (!top || top.char !== pairs[char]) {
68
+ return {
69
+ balanced: false,
70
+ message: `Error de balanceo: Se encontró '${char}' en línea ${lineNum} pero se esperaba cierre para '${top ? top.char : 'ninguno'}' (línea ${top ? top.line : 'N/A'}).`
71
+ }
72
+ }
73
+ }
74
+ }
75
+
76
+ if (stack.length > 0) {
77
+ const unclosed = stack.pop()!
78
+ return {
79
+ balanced: false,
80
+ message: `Error de balanceo: El token '${unclosed.char}' abierto en la línea ${unclosed.line} nunca fue cerrado.`
81
+ }
82
+ }
83
+
84
+ return { balanced: true }
85
+ }
86
+
51
87
  for (const file of filesToAudit) {
52
- const ext = path.extname(file)
88
+ const ext = path.extname(file).toLowerCase()
53
89
  const relFile = path.relative(projectRoot, file)
54
90
 
55
91
  try {
56
92
  const content = fs.readFileSync(file, "utf-8")
57
93
 
58
- // 1. Validar JSON
94
+ // 1. JSON
59
95
  if (ext === ".json") {
60
96
  try {
61
97
  JSON.parse(content)
@@ -70,51 +106,35 @@ export default tool({
70
106
  }
71
107
  }
72
108
 
73
- // 2. Validar JavaScript / Google Apps Script
109
+ // 2. JavaScript / Google Apps Script (.js, .jsx, .gs)
74
110
  else if (ext === ".js" || ext === ".jsx" || ext === ".gs") {
75
111
  try {
76
112
  execSync(`node --check "${file}"`, { stdio: "pipe" })
77
113
  } catch (err: any) {
78
114
  const stderr = err.stderr?.toString() || err.message || ""
79
- // Parse line number from node check, e.g. "index.js:5" or "line 5"
80
115
  const lineMatch = stderr.match(/:(\d+)\r?\n/m) || stderr.match(/line (\d+)/i)
81
116
  diagnostics.push({
82
117
  file: relFile,
83
- type: "JavaScript Syntax Error",
118
+ type: "JS/GS Syntax Error",
84
119
  line: lineMatch ? parseInt(lineMatch[1]) : undefined,
85
120
  message: stderr.split("\n")[0] || "Syntax error detected"
86
121
  })
87
122
  }
88
123
  }
89
124
 
90
- // 3. Validar TypeScript
125
+ // 3. TypeScript / TSX
91
126
  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) {}
127
+ const balanceCheck = hasBalancedTokens(content)
128
+ if (!balanceCheck.balanced) {
129
+ diagnostics.push({
130
+ file: relFile,
131
+ type: "TypeScript Syntax/Token Mismatch",
132
+ message: balanceCheck.message || "Falla de balanceo en tokens estructurados."
133
+ })
134
+ }
115
135
  }
116
136
 
117
- // 4. Validar Python
137
+ // 4. Python (.py)
118
138
  else if (ext === ".py") {
119
139
  try {
120
140
  execSync(`python -m py_compile "${file}"`, { stdio: "pipe" })
@@ -130,7 +150,7 @@ export default tool({
130
150
  }
131
151
  }
132
152
 
133
- // 5. Validar HTML
153
+ // 5. HTML
134
154
  else if (ext === ".html") {
135
155
  const unclosedTags: string[] = []
136
156
  const tags = content.match(/<[^>]+>/g) || []
@@ -161,10 +181,95 @@ export default tool({
161
181
  }
162
182
  }
163
183
 
184
+ // 6. Shell Scripts (.sh, .bash)
185
+ else if (ext === ".sh" || ext === ".bash") {
186
+ try {
187
+ execSync(`bash -n "${file}"`, { stdio: "pipe" })
188
+ } catch (err: any) {
189
+ const stderr = err.stderr?.toString() || err.message || ""
190
+ const lineMatch = stderr.match(/: line (\d+):/i) || stderr.match(/:(\d+):/i)
191
+ diagnostics.push({
192
+ file: relFile,
193
+ type: "Shell Script Syntax Error",
194
+ line: lineMatch ? parseInt(lineMatch[1]) : undefined,
195
+ message: stderr.split("\n")[0] || "Shell script syntax check failed"
196
+ })
197
+ }
198
+ }
199
+
200
+ // 7. CSS
201
+ else if (ext === ".css") {
202
+ const balanceCheck = hasBalancedTokens(content)
203
+ if (!balanceCheck.balanced) {
204
+ diagnostics.push({
205
+ file: relFile,
206
+ type: "CSS Rule Mismatch",
207
+ message: `Falla de balanceo en CSS: ${balanceCheck.message}`
208
+ })
209
+ }
210
+ }
211
+
212
+ // 8. C / C++ (.c, .cpp, .h, .hpp)
213
+ else if (ext === ".c" || ext === ".cpp" || ext === ".h" || ext === ".hpp") {
214
+ const balanceCheck = hasBalancedTokens(content)
215
+ if (!balanceCheck.balanced) {
216
+ diagnostics.push({
217
+ file: relFile,
218
+ type: "C/C++ Syntax Token Mismatch",
219
+ message: `Falla estructural C/C++: ${balanceCheck.message}`
220
+ })
221
+ }
222
+ }
223
+
224
+ // 9. YAML / YML
225
+ else if (ext === ".yaml" || ext === ".yml") {
226
+ // Chequeo básico de indentación y formato YAML sin requerir librerías externas pesadas
227
+ const lines = content.split("\n")
228
+ lines.forEach((line, idx) => {
229
+ const spaces = line.match(/^(\s*)/)?.[1].length || 0
230
+ if (spaces % 2 !== 0 && line.trim().length > 0 && !line.trim().startsWith("#")) {
231
+ diagnostics.push({
232
+ file: relFile,
233
+ type: "YAML Indentation Warning",
234
+ line: idx + 1,
235
+ message: `Indentación sospechosa (${spaces} espacios). YAML prefiere múltiplos de 2.`
236
+ })
237
+ }
238
+ })
239
+ }
240
+
241
+ // 10. PHP
242
+ else if (ext === ".php") {
243
+ try {
244
+ execSync(`php -l "${file}"`, { stdio: "pipe" })
245
+ } catch (err: any) {
246
+ const stderr = err.stderr?.toString() || err.message || ""
247
+ const lineMatch = stderr.match(/on line (\d+)/i)
248
+ diagnostics.push({
249
+ file: relFile,
250
+ type: "PHP Syntax Error",
251
+ line: lineMatch ? parseInt(lineMatch[1]) : undefined,
252
+ message: stderr.split("\n")[0] || "PHP syntax check failed"
253
+ })
254
+ }
255
+ }
256
+
257
+ // 11. Markdown (.md)
258
+ else if (ext === ".md") {
259
+ const openBlocks = (content.match(/^```/gm) || []).length
260
+ if (openBlocks % 2 !== 0) {
261
+ diagnostics.push({
262
+ file: relFile,
263
+ type: "Markdown Fenced Block Mismatch",
264
+ message: "Se detectó un bloque de código fenced (```) sin cerrar en el archivo markdown."
265
+ })
266
+ }
267
+ }
268
+
164
269
  } catch (e: any) {
165
270
  diagnostics.push({
166
271
  file: relFile,
167
- type: "Read Error",
272
+ type: "Read/Compile Error",
168
273
  message: `Imposible leer o analizar el archivo: ${e.message}`
169
274
  })
170
275
  }
@@ -173,7 +278,7 @@ export default tool({
173
278
  if (diagnostics.length > 0) {
174
279
  return JSON.stringify({
175
280
  status: "FAILED",
176
- message: `❌ VALIDACIÓN DE SINTAXIS FALLIDA: Se detectaron ${diagnostics.length} errores de sintaxis o balanceo en los archivos modificados.`,
281
+ message: `❌ VALIDACIÓN DE SINTAXIS MULTI-TECNOLOGÍA FALLIDA: Se detectaron ${diagnostics.length} errores estáticos o de formato en los archivos modificados.`,
177
282
  diagnostics
178
283
  }, null, 2)
179
284
  }