snapcontext 3.3.0__tar.gz → 3.4.0__tar.gz
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.
- {snapcontext-3.3.0/snapcontext.egg-info → snapcontext-3.4.0}/PKG-INFO +39 -2
- {snapcontext-3.3.0 → snapcontext-3.4.0}/README.md +38 -1
- {snapcontext-3.3.0 → snapcontext-3.4.0}/agentes.py +173 -44
- {snapcontext-3.3.0 → snapcontext-3.4.0}/orquestador.py +3 -0
- {snapcontext-3.3.0 → snapcontext-3.4.0}/pyproject.toml +1 -1
- {snapcontext-3.3.0 → snapcontext-3.4.0/snapcontext.egg-info}/PKG-INFO +39 -2
- {snapcontext-3.3.0 → snapcontext-3.4.0}/snapcontext.egg-info/SOURCES.txt +1 -0
- {snapcontext-3.3.0 → snapcontext-3.4.0}/snapcontext.py +149 -3
- {snapcontext-3.3.0 → snapcontext-3.4.0}/tests/test_auto_017.py +1 -1
- {snapcontext-3.3.0 → snapcontext-3.4.0}/tests/test_diagnostico_310.py +1 -1
- {snapcontext-3.3.0 → snapcontext-3.4.0}/tests/test_diff_editor_210.py +1 -1
- {snapcontext-3.3.0 → snapcontext-3.4.0}/tests/test_editor_330.py +1 -1
- {snapcontext-3.3.0 → snapcontext-3.4.0}/tests/test_editor_ast_220.py +1 -1
- {snapcontext-3.3.0 → snapcontext-3.4.0}/tests/test_editor_propio_200.py +1 -1
- {snapcontext-3.3.0 → snapcontext-3.4.0}/tests/test_exe_150.py +1 -1
- {snapcontext-3.3.0 → snapcontext-3.4.0}/tests/test_jetbrains_170.py +1 -1
- {snapcontext-3.3.0 → snapcontext-3.4.0}/tests/test_mcp_014.py +1 -1
- {snapcontext-3.3.0 → snapcontext-3.4.0}/tests/test_mcp_140.py +1 -1
- {snapcontext-3.3.0 → snapcontext-3.4.0}/tests/test_memoria_015.py +1 -1
- {snapcontext-3.3.0 → snapcontext-3.4.0}/tests/test_memoria_300.py +1 -1
- {snapcontext-3.3.0 → snapcontext-3.4.0}/tests/test_permisos_013.py +1 -1
- {snapcontext-3.3.0 → snapcontext-3.4.0}/tests/test_plan_012.py +1 -1
- {snapcontext-3.3.0 → snapcontext-3.4.0}/tests/test_plan_mcp_230.py +1 -1
- {snapcontext-3.3.0 → snapcontext-3.4.0}/tests/test_v010.py +1 -1
- {snapcontext-3.3.0 → snapcontext-3.4.0}/tests/test_validacion_130.py +1 -1
- snapcontext-3.4.0/tests/test_validacion_sintaxis_340.py +195 -0
- {snapcontext-3.3.0 → snapcontext-3.4.0}/tests/test_vscode_016.py +1 -1
- {snapcontext-3.3.0 → snapcontext-3.4.0}/tests/test_web_160.py +1 -1
- {snapcontext-3.3.0 → snapcontext-3.4.0}/LICENSE +0 -0
- {snapcontext-3.3.0 → snapcontext-3.4.0}/setup.cfg +0 -0
- {snapcontext-3.3.0 → snapcontext-3.4.0}/snapcontext.egg-info/__pycache__/top_level.cpython-313.pyc +0 -0
- {snapcontext-3.3.0 → snapcontext-3.4.0}/snapcontext.egg-info/dependency_links.txt +0 -0
- {snapcontext-3.3.0 → snapcontext-3.4.0}/snapcontext.egg-info/entry_points.txt +0 -0
- {snapcontext-3.3.0 → snapcontext-3.4.0}/snapcontext.egg-info/requires.txt +0 -0
- {snapcontext-3.3.0 → snapcontext-3.4.0}/snapcontext.egg-info/top_level.txt +0 -0
- {snapcontext-3.3.0 → snapcontext-3.4.0}/tests/test_agentes.py +0 -0
- {snapcontext-3.3.0 → snapcontext-3.4.0}/tests/test_bienvenida_311.py +0 -0
- {snapcontext-3.3.0 → snapcontext-3.4.0}/tests/test_deteccion.py +0 -0
- {snapcontext-3.3.0 → snapcontext-3.4.0}/tests/test_embeddings_110.py +0 -0
- {snapcontext-3.3.0 → snapcontext-3.4.0}/tests/test_orquestador.py +0 -0
- {snapcontext-3.3.0 → snapcontext-3.4.0}/tests/test_vscode_ts_320.py +0 -0
- {snapcontext-3.3.0 → snapcontext-3.4.0}/tests/test_web_120.py +0 -0
- {snapcontext-3.3.0 → snapcontext-3.4.0}/web/__init__.py +0 -0
- {snapcontext-3.3.0 → snapcontext-3.4.0}/web/app.py +0 -0
- {snapcontext-3.3.0 → snapcontext-3.4.0}/web/static/index.html +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: snapcontext
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.4.0
|
|
4
4
|
Summary: SnapContext: Asistente de IA con contexto automático para desarrollo
|
|
5
5
|
Author-email: SnapContext Contributors <tu@email.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -45,7 +45,7 @@ Dynamic: license-file
|
|
|
45
45
|
|
|
46
46
|
# SnapContext
|
|
47
47
|
|
|
48
|
-

|
|
49
49
|
[](https://pypi.org/project/snapcontext/)
|
|
50
50
|
[](https://github.com/NicolasBruña24/snapcontext/actions)
|
|
51
51
|

|
|
@@ -199,6 +199,37 @@ snapcontext "corregir tipado en login" --editor propio --modo-edicion parche
|
|
|
199
199
|
|
|
200
200
|
# Refactorización estructural con AST
|
|
201
201
|
snapcontext "renombrar la variable 'usuario' a 'cuenta'" --editor propio --modo-edicion ast
|
|
202
|
+
|
|
203
|
+
# Validación de sintaxis desactivada (comportamiento previo a v3.4.0)
|
|
204
|
+
snapcontext "corregir tipado" --editor propio --no-validar-sintaxis
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Validación de sintaxis antes de guardar (v3.4.0)
|
|
208
|
+
|
|
209
|
+
Cuando uses `--editor propio`, SnapContext **valida la sintaxis** del código que
|
|
210
|
+
ha generado la IA **antes de escribirlo en disco**. Si la validación falla, el
|
|
211
|
+
cambio se rechaza y se le envía el error al proveedor para que lo corrija,
|
|
212
|
+
repitiéndose hasta `--max-intentos-validacion` intentos (por defecto 3). Si tras
|
|
213
|
+
esos intentos el código sigue roto, se **cancela la edición** y el archivo queda
|
|
214
|
+
intacto.
|
|
215
|
+
|
|
216
|
+
- Se detecta el lenguaje del archivo y se usa el validador adecuado:
|
|
217
|
+
Python (`py_compile`), JavaScript/TypeScript (`node --check`), Dart
|
|
218
|
+
(`dart analyze` → `dart format`), Go (`go build -n` → `gofmt -e`), Rust
|
|
219
|
+
(`rustc --parse-only`), Java (`javac -Xlint:none`), C/C++ (`gcc`/`clang
|
|
220
|
+
-fsyntax-only`). Si no hay validador o comando disponible, se omite la
|
|
221
|
+
validación (solo aviso en logs con `--depurar`).
|
|
222
|
+
- La validación se hace sobre un **archivo temporal**, nunca toca el original.
|
|
223
|
+
- Flag `--validar` (por defecto activado) / `--no-validar-sintaxis` para
|
|
224
|
+
desactivarla. Nota: el nombre usa `--no-validar-sintaxis` porque
|
|
225
|
+
`--no-validar` ya es alias de `--iniciar-proyecto`.
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
# Desactivar la validación por completo (comportamiento anterior)
|
|
229
|
+
snapcontext "..." --editor propio --no-validar-sintaxis
|
|
230
|
+
|
|
231
|
+
# Ajustar el número de reintentos
|
|
232
|
+
snapcontext "..." --editor propio --max-intentos-validacion 5
|
|
202
233
|
```
|
|
203
234
|
|
|
204
235
|
## 🧭 Modos y alias
|
|
@@ -1013,6 +1044,12 @@ locales y te deja elegir con las flechas:
|
|
|
1013
1044
|
| `--url-defecto` | `http://localhost:5000` | URL para abrir el navegador si Flutter no reporta una |
|
|
1014
1045
|
| `--comando-test` | `"flutter test"` | Comando del bucle de pruebas |
|
|
1015
1046
|
| `--max-iteraciones` | `3` | Iteraciones máximas del bucle |
|
|
1047
|
+
| `--validar` | on | Valida la sintaxis del código antes de guardar en el editor propio |
|
|
1048
|
+
| `--no-validar-sintaxis` | off | Desactiva la validación de sintaxis del editor propio |
|
|
1049
|
+
| `--max-intentos-validacion` | `3` | Intentos de validación de sintaxis antes de cancelar la edición |
|
|
1050
|
+
|
|
1051
|
+
> **Nota:** el flag negativo de validación de sintaxis se llama `--no-validar-sintaxis`
|
|
1052
|
+
> porque `--no-validar` ya existe como alias de `--iniciar-proyecto`.
|
|
1016
1053
|
|
|
1017
1054
|
---
|
|
1018
1055
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# SnapContext
|
|
2
2
|
|
|
3
|
-

|
|
4
4
|
[](https://pypi.org/project/snapcontext/)
|
|
5
5
|
[](https://github.com/NicolasBruña24/snapcontext/actions)
|
|
6
6
|

|
|
@@ -154,6 +154,37 @@ snapcontext "corregir tipado en login" --editor propio --modo-edicion parche
|
|
|
154
154
|
|
|
155
155
|
# Refactorización estructural con AST
|
|
156
156
|
snapcontext "renombrar la variable 'usuario' a 'cuenta'" --editor propio --modo-edicion ast
|
|
157
|
+
|
|
158
|
+
# Validación de sintaxis desactivada (comportamiento previo a v3.4.0)
|
|
159
|
+
snapcontext "corregir tipado" --editor propio --no-validar-sintaxis
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Validación de sintaxis antes de guardar (v3.4.0)
|
|
163
|
+
|
|
164
|
+
Cuando uses `--editor propio`, SnapContext **valida la sintaxis** del código que
|
|
165
|
+
ha generado la IA **antes de escribirlo en disco**. Si la validación falla, el
|
|
166
|
+
cambio se rechaza y se le envía el error al proveedor para que lo corrija,
|
|
167
|
+
repitiéndose hasta `--max-intentos-validacion` intentos (por defecto 3). Si tras
|
|
168
|
+
esos intentos el código sigue roto, se **cancela la edición** y el archivo queda
|
|
169
|
+
intacto.
|
|
170
|
+
|
|
171
|
+
- Se detecta el lenguaje del archivo y se usa el validador adecuado:
|
|
172
|
+
Python (`py_compile`), JavaScript/TypeScript (`node --check`), Dart
|
|
173
|
+
(`dart analyze` → `dart format`), Go (`go build -n` → `gofmt -e`), Rust
|
|
174
|
+
(`rustc --parse-only`), Java (`javac -Xlint:none`), C/C++ (`gcc`/`clang
|
|
175
|
+
-fsyntax-only`). Si no hay validador o comando disponible, se omite la
|
|
176
|
+
validación (solo aviso en logs con `--depurar`).
|
|
177
|
+
- La validación se hace sobre un **archivo temporal**, nunca toca el original.
|
|
178
|
+
- Flag `--validar` (por defecto activado) / `--no-validar-sintaxis` para
|
|
179
|
+
desactivarla. Nota: el nombre usa `--no-validar-sintaxis` porque
|
|
180
|
+
`--no-validar` ya es alias de `--iniciar-proyecto`.
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
# Desactivar la validación por completo (comportamiento anterior)
|
|
184
|
+
snapcontext "..." --editor propio --no-validar-sintaxis
|
|
185
|
+
|
|
186
|
+
# Ajustar el número de reintentos
|
|
187
|
+
snapcontext "..." --editor propio --max-intentos-validacion 5
|
|
157
188
|
```
|
|
158
189
|
|
|
159
190
|
## 🧭 Modos y alias
|
|
@@ -968,6 +999,12 @@ locales y te deja elegir con las flechas:
|
|
|
968
999
|
| `--url-defecto` | `http://localhost:5000` | URL para abrir el navegador si Flutter no reporta una |
|
|
969
1000
|
| `--comando-test` | `"flutter test"` | Comando del bucle de pruebas |
|
|
970
1001
|
| `--max-iteraciones` | `3` | Iteraciones máximas del bucle |
|
|
1002
|
+
| `--validar` | on | Valida la sintaxis del código antes de guardar en el editor propio |
|
|
1003
|
+
| `--no-validar-sintaxis` | off | Desactiva la validación de sintaxis del editor propio |
|
|
1004
|
+
| `--max-intentos-validacion` | `3` | Intentos de validación de sintaxis antes de cancelar la edición |
|
|
1005
|
+
|
|
1006
|
+
> **Nota:** el flag negativo de validación de sintaxis se llama `--no-validar-sintaxis`
|
|
1007
|
+
> porque `--no-validar` ya existe como alias de `--iniciar-proyecto`.
|
|
971
1008
|
|
|
972
1009
|
---
|
|
973
1010
|
|
|
@@ -232,9 +232,55 @@ class AgenteEditorPropio:
|
|
|
232
232
|
return sc._editor_ast(archivo, tarea, directorio=directorio,
|
|
233
233
|
proveedor=proveedor, modelo=modelo)
|
|
234
234
|
|
|
235
|
+
@staticmethod
|
|
236
|
+
def _aplicar_parche_preview(parche: str, contenido_actual: str):
|
|
237
|
+
"""Aplica ``parche`` a ``contenido_actual`` **en memoria** para poder
|
|
238
|
+
validar la sintaxis del resultado sin tocar el archivo real.
|
|
239
|
+
|
|
240
|
+
Reutiliza la segmentación de hunks de ``_parsear_hunks`` y aplica cada
|
|
241
|
+
hunk con tolerancia a pequeños desfases. Devuelve ``(resultado, n)``
|
|
242
|
+
donde ``n`` es el número de hunks aplicados (0 ⇒ no se pudo reproducir).
|
|
243
|
+
"""
|
|
244
|
+
import snapcontext as sc
|
|
245
|
+
|
|
246
|
+
resultado = (contenido_actual or "").splitlines()
|
|
247
|
+
desplazamiento = 0
|
|
248
|
+
aplicados = 0
|
|
249
|
+
for inicio_orig, cambios in sc._parsear_hunks(parche):
|
|
250
|
+
base = max(inicio_orig - 1 + desplazamiento, 0)
|
|
251
|
+
n_borrados = sum(1 for marca, _ in cambios if marca != "+")
|
|
252
|
+
nuevos = [texto for marca, texto in cambios if marca != "-"]
|
|
253
|
+
|
|
254
|
+
posicion = -1
|
|
255
|
+
for delta in (0, 1, -1, 2, -2, 3, -3, 5, -5, 10, -10):
|
|
256
|
+
candidato = base + delta
|
|
257
|
+
if candidato < 0 or candidato > len(resultado):
|
|
258
|
+
continue
|
|
259
|
+
idx = candidato
|
|
260
|
+
encaja = True
|
|
261
|
+
for marca, texto in cambios:
|
|
262
|
+
if marca == "+":
|
|
263
|
+
continue
|
|
264
|
+
if idx >= len(resultado) or resultado[idx] != texto:
|
|
265
|
+
encaja = False
|
|
266
|
+
break
|
|
267
|
+
idx += 1
|
|
268
|
+
if encaja:
|
|
269
|
+
posicion = candidato
|
|
270
|
+
break
|
|
271
|
+
if posicion < 0:
|
|
272
|
+
continue
|
|
273
|
+
|
|
274
|
+
resultado[posicion:posicion + n_borrados] = nuevos
|
|
275
|
+
desplazamiento += len(nuevos) - n_borrados
|
|
276
|
+
aplicados += 1
|
|
277
|
+
|
|
278
|
+
return "\n".join(resultado) + "\n", aplicados
|
|
279
|
+
|
|
235
280
|
def _aplicar_modo_parche(self, archivo: str, mensaje: str,
|
|
236
281
|
contenido_actual: str, modelo: Optional[str],
|
|
237
|
-
directorio: str
|
|
282
|
+
directorio: str, validar: bool = True,
|
|
283
|
+
max_intentos_validacion: int = 3) -> bool:
|
|
238
284
|
"""Intenta editar `archivo` pidiendo un parche unificado al proveedor."""
|
|
239
285
|
import snapcontext as sc
|
|
240
286
|
|
|
@@ -243,57 +289,134 @@ class AgenteEditorPropio:
|
|
|
243
289
|
lenguaje = sc._lenguaje_archivo(archivo, contenido_actual) or "?"
|
|
244
290
|
num_lineas = (contenido_actual.count("\n") + 1
|
|
245
291
|
if contenido_actual else 0)
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
292
|
+
max_val = max(1, int(max_intentos_validacion))
|
|
293
|
+
|
|
294
|
+
error_msj = ""
|
|
295
|
+
for intento in range(1, max_val + 1):
|
|
296
|
+
prompt = (
|
|
297
|
+
f"Genera un parche unificado (unified diff) que modifique el archivo "
|
|
298
|
+
f"para cumplir con la tarea con precisión máxima.\n"
|
|
299
|
+
f"El parche debe ser aplicable con 'patch -p1' o 'git apply' "
|
|
300
|
+
f"(encabezados --- a/{archivo} y +++ b/{archivo}).\n\n"
|
|
301
|
+
f"Tarea: {mensaje}\n"
|
|
302
|
+
f"Archivo: {archivo} (lenguaje: {lenguaje}, {num_lineas} líneas)\n\n"
|
|
303
|
+
f"Instrucciones:\n"
|
|
304
|
+
f"- Incluye suficiente contexto (3 líneas) para anclar cada hunk.\n"
|
|
305
|
+
f"- Conserva el estilo existente (indentación, comillas).\n"
|
|
306
|
+
f"- Modifica SOLO lo necesario; no reorganices el resto del archivo.\n"
|
|
307
|
+
)
|
|
308
|
+
if error_msj:
|
|
309
|
+
prompt += (
|
|
310
|
+
f"\nTu intento anterior produjo un error de sintaxis que debes "
|
|
311
|
+
f"corregir:\n{error_msj}\n\n"
|
|
312
|
+
f"Mantén el objetivo de la tarea pero arregla ese error.\n"
|
|
313
|
+
)
|
|
314
|
+
prompt += (
|
|
315
|
+
f"\nContenido actual completo:\n```{lenguaje}\n{contenido_actual}\n```\n\n"
|
|
316
|
+
f"Devuelve ÚNICAMENTE el diff unificado, sin explicaciones ni rodeos."
|
|
317
|
+
)
|
|
318
|
+
respuesta = sc._enviar_al_proveedor(
|
|
319
|
+
proveedor, modelo, [{"role": "user", "content": prompt}])
|
|
320
|
+
diff_limpio = respuesta
|
|
321
|
+
if "--- " in diff_limpio and "+++ " in diff_limpio:
|
|
322
|
+
idx_inicio = diff_limpio.find("--- ")
|
|
323
|
+
diff_limpio = diff_limpio[idx_inicio:]
|
|
324
|
+
if "```" in diff_limpio:
|
|
325
|
+
diff_limpio = diff_limpio[:diff_limpio.find("```")]
|
|
326
|
+
if not ("--- " in diff_limpio and "+++ " in diff_limpio
|
|
327
|
+
and "@@" in diff_limpio):
|
|
328
|
+
return False
|
|
329
|
+
|
|
330
|
+
if not validar:
|
|
331
|
+
# v3.3.0: validación previa + resolución de conflictos.
|
|
332
|
+
return self.aplicar_parche(diff_limpio, directorio,
|
|
333
|
+
contenido_esperado=contenido_actual)
|
|
334
|
+
|
|
335
|
+
# v3.4.0: validar la sintaxis del contenido resultante.
|
|
336
|
+
preview, aplicados = self._aplicar_parche_preview(
|
|
337
|
+
diff_limpio, contenido_actual)
|
|
338
|
+
if aplicados == 0:
|
|
339
|
+
# No se pudo reproducir en memoria → se omite la validación.
|
|
340
|
+
return self.aplicar_parche(diff_limpio, directorio,
|
|
341
|
+
contenido_esperado=contenido_actual)
|
|
342
|
+
|
|
343
|
+
sc.info(f"Validando sintaxis de {archivo}...")
|
|
344
|
+
exito, err = sc._validar_sintaxis(archivo, preview, directorio)
|
|
345
|
+
if exito:
|
|
346
|
+
sc.exito("Sintaxis válida.")
|
|
347
|
+
return self.aplicar_parche(diff_limpio, directorio,
|
|
348
|
+
contenido_esperado=contenido_actual)
|
|
349
|
+
|
|
350
|
+
error_msj = err
|
|
351
|
+
if intento < max_val:
|
|
352
|
+
sc.error(
|
|
353
|
+
f"Error de sintaxis: {err}. "
|
|
354
|
+
f"Reintentando ({intento}/{max_val})...")
|
|
355
|
+
else:
|
|
356
|
+
sc.error(
|
|
357
|
+
f"No se pudo validar tras {max_val} intentos. "
|
|
358
|
+
f"Edición cancelada.")
|
|
359
|
+
return False
|
|
272
360
|
return False
|
|
273
361
|
|
|
274
362
|
def _aplicar_modo_sobrescribir(self, archivo: str, mensaje: str,
|
|
275
363
|
contenido_actual: str, modelo: Optional[str],
|
|
276
|
-
directorio: str
|
|
364
|
+
directorio: str, validar: bool = True,
|
|
365
|
+
max_intentos_validacion: int = 3) -> bool:
|
|
277
366
|
"""Sobrescribe `archivo` con el código completo que devuelve el proveedor."""
|
|
278
367
|
import snapcontext as sc
|
|
279
368
|
|
|
280
369
|
pref = sc.cargar_configuracion()
|
|
281
370
|
proveedor = pref.get("provider") or sc.PROVEEDOR_DEFECTO
|
|
282
371
|
lenguaje = sc._lenguaje_archivo(archivo, contenido_actual) or "?"
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
372
|
+
max_val = max(1, int(max_intentos_validacion))
|
|
373
|
+
|
|
374
|
+
error_msj = ""
|
|
375
|
+
nuevo_contenido = ""
|
|
376
|
+
for intento in range(1, max_val + 1):
|
|
377
|
+
prompt = (
|
|
378
|
+
f"Modifica el siguiente archivo ({lenguaje}) para cumplir con la "
|
|
379
|
+
f"tarea, conservando el estilo existente y cambiando solo lo necesario.\n\n"
|
|
380
|
+
f"Tarea: {mensaje}\n"
|
|
381
|
+
f"Archivo: {archivo} (lenguaje: {lenguaje})\n"
|
|
382
|
+
)
|
|
383
|
+
if error_msj:
|
|
384
|
+
prompt += (
|
|
385
|
+
f"\nTu intento anterior produjo un error de sintaxis que debes "
|
|
386
|
+
f"corregir:\n{error_msj}\n\n"
|
|
387
|
+
f"Mantén el objetivo de la tarea pero arregla ese error.\n"
|
|
388
|
+
)
|
|
389
|
+
prompt += (
|
|
390
|
+
f"\nContenido actual completo:\n```{lenguaje}\n{contenido_actual}\n```\n\n"
|
|
391
|
+
f"Devuelve ÚNICAMENTE el código completo resultante, sin explicaciones ni markdown."
|
|
392
|
+
)
|
|
393
|
+
respuesta = sc._enviar_al_proveedor(
|
|
394
|
+
proveedor, modelo, [{"role": "user", "content": prompt}])
|
|
395
|
+
nuevo_contenido = respuesta
|
|
396
|
+
if nuevo_contenido.startswith("```"):
|
|
397
|
+
lineas = nuevo_contenido.splitlines()
|
|
398
|
+
if len(lineas) >= 2 and lineas[-1].startswith("```"):
|
|
399
|
+
nuevo_contenido = "\n".join(lineas[1:-1])
|
|
400
|
+
|
|
401
|
+
if not validar:
|
|
402
|
+
break
|
|
403
|
+
|
|
404
|
+
sc.info(f"Validando sintaxis de {archivo}...")
|
|
405
|
+
exito, err = sc._validar_sintaxis(archivo, nuevo_contenido, directorio)
|
|
406
|
+
if exito:
|
|
407
|
+
sc.exito("Sintaxis válida.")
|
|
408
|
+
break
|
|
409
|
+
error_msj = err
|
|
410
|
+
if intento < max_val:
|
|
411
|
+
sc.error(
|
|
412
|
+
f"Error de sintaxis: {err}. "
|
|
413
|
+
f"Reintentando ({intento}/{max_val})...")
|
|
414
|
+
else:
|
|
415
|
+
sc.error(
|
|
416
|
+
f"No se pudo validar tras {max_val} intentos. "
|
|
417
|
+
f"Edición cancelada.")
|
|
418
|
+
return False
|
|
419
|
+
|
|
297
420
|
return self.sobrescribir(archivo, nuevo_contenido, directorio)
|
|
298
421
|
|
|
299
422
|
def ejecutar(
|
|
@@ -303,6 +426,8 @@ class AgenteEditorPropio:
|
|
|
303
426
|
directorio: str = ".",
|
|
304
427
|
modo_edicion: str = "auto",
|
|
305
428
|
modelo: Optional[str] = None,
|
|
429
|
+
validar: bool = True,
|
|
430
|
+
max_intentos_validacion: int = 3,
|
|
306
431
|
) -> bool:
|
|
307
432
|
"""Aplica las modificaciones para `archivos` con el `mensaje` especificado.
|
|
308
433
|
|
|
@@ -353,10 +478,14 @@ class AgenteEditorPropio:
|
|
|
353
478
|
str(raiz), modelo)
|
|
354
479
|
elif estrategia == "parche":
|
|
355
480
|
conseguido = self._aplicar_modo_parche(
|
|
356
|
-
arch, mensaje, contenido_actual, modelo, str(raiz)
|
|
481
|
+
arch, mensaje, contenido_actual, modelo, str(raiz),
|
|
482
|
+
validar=validar,
|
|
483
|
+
max_intentos_validacion=max_intentos_validacion)
|
|
357
484
|
elif estrategia == "sobrescribir":
|
|
358
485
|
conseguido = self._aplicar_modo_sobrescribir(
|
|
359
|
-
arch, mensaje, contenido_actual, modelo, str(raiz)
|
|
486
|
+
arch, mensaje, contenido_actual, modelo, str(raiz),
|
|
487
|
+
validar=validar,
|
|
488
|
+
max_intentos_validacion=max_intentos_validacion)
|
|
360
489
|
else:
|
|
361
490
|
conseguido = False
|
|
362
491
|
except Exception as exc:
|
|
@@ -190,6 +190,9 @@ class Orquestador:
|
|
|
190
190
|
directorio=str(raiz),
|
|
191
191
|
modo_edicion=modo_ed,
|
|
192
192
|
modelo=getattr(args, "modelo", None),
|
|
193
|
+
validar=getattr(args, "validar", True),
|
|
194
|
+
max_intentos_validacion=getattr(
|
|
195
|
+
args, "max_intentos_validacion", 3),
|
|
193
196
|
)
|
|
194
197
|
self._emitir_tipo("editor", accion="fin", ok=ok)
|
|
195
198
|
else:
|
|
@@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
|
|
|
6
6
|
# Nombre verificado disponible en PyPI (18/08/2026): pypi.org/pypi/snapcontext → 404.
|
|
7
7
|
# Alternativas si algún día estuviera ocupado: "snapcontext-cli" o "snapcontext-tool".
|
|
8
8
|
name = "snapcontext"
|
|
9
|
-
version = "3.
|
|
9
|
+
version = "3.4.0"
|
|
10
10
|
description = "SnapContext: Asistente de IA con contexto automático para desarrollo"
|
|
11
11
|
readme = "README.md"
|
|
12
12
|
requires-python = ">=3.9"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: snapcontext
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.4.0
|
|
4
4
|
Summary: SnapContext: Asistente de IA con contexto automático para desarrollo
|
|
5
5
|
Author-email: SnapContext Contributors <tu@email.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -45,7 +45,7 @@ Dynamic: license-file
|
|
|
45
45
|
|
|
46
46
|
# SnapContext
|
|
47
47
|
|
|
48
|
-

|
|
49
49
|
[](https://pypi.org/project/snapcontext/)
|
|
50
50
|
[](https://github.com/NicolasBruña24/snapcontext/actions)
|
|
51
51
|

|
|
@@ -199,6 +199,37 @@ snapcontext "corregir tipado en login" --editor propio --modo-edicion parche
|
|
|
199
199
|
|
|
200
200
|
# Refactorización estructural con AST
|
|
201
201
|
snapcontext "renombrar la variable 'usuario' a 'cuenta'" --editor propio --modo-edicion ast
|
|
202
|
+
|
|
203
|
+
# Validación de sintaxis desactivada (comportamiento previo a v3.4.0)
|
|
204
|
+
snapcontext "corregir tipado" --editor propio --no-validar-sintaxis
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Validación de sintaxis antes de guardar (v3.4.0)
|
|
208
|
+
|
|
209
|
+
Cuando uses `--editor propio`, SnapContext **valida la sintaxis** del código que
|
|
210
|
+
ha generado la IA **antes de escribirlo en disco**. Si la validación falla, el
|
|
211
|
+
cambio se rechaza y se le envía el error al proveedor para que lo corrija,
|
|
212
|
+
repitiéndose hasta `--max-intentos-validacion` intentos (por defecto 3). Si tras
|
|
213
|
+
esos intentos el código sigue roto, se **cancela la edición** y el archivo queda
|
|
214
|
+
intacto.
|
|
215
|
+
|
|
216
|
+
- Se detecta el lenguaje del archivo y se usa el validador adecuado:
|
|
217
|
+
Python (`py_compile`), JavaScript/TypeScript (`node --check`), Dart
|
|
218
|
+
(`dart analyze` → `dart format`), Go (`go build -n` → `gofmt -e`), Rust
|
|
219
|
+
(`rustc --parse-only`), Java (`javac -Xlint:none`), C/C++ (`gcc`/`clang
|
|
220
|
+
-fsyntax-only`). Si no hay validador o comando disponible, se omite la
|
|
221
|
+
validación (solo aviso en logs con `--depurar`).
|
|
222
|
+
- La validación se hace sobre un **archivo temporal**, nunca toca el original.
|
|
223
|
+
- Flag `--validar` (por defecto activado) / `--no-validar-sintaxis` para
|
|
224
|
+
desactivarla. Nota: el nombre usa `--no-validar-sintaxis` porque
|
|
225
|
+
`--no-validar` ya es alias de `--iniciar-proyecto`.
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
# Desactivar la validación por completo (comportamiento anterior)
|
|
229
|
+
snapcontext "..." --editor propio --no-validar-sintaxis
|
|
230
|
+
|
|
231
|
+
# Ajustar el número de reintentos
|
|
232
|
+
snapcontext "..." --editor propio --max-intentos-validacion 5
|
|
202
233
|
```
|
|
203
234
|
|
|
204
235
|
## 🧭 Modos y alias
|
|
@@ -1013,6 +1044,12 @@ locales y te deja elegir con las flechas:
|
|
|
1013
1044
|
| `--url-defecto` | `http://localhost:5000` | URL para abrir el navegador si Flutter no reporta una |
|
|
1014
1045
|
| `--comando-test` | `"flutter test"` | Comando del bucle de pruebas |
|
|
1015
1046
|
| `--max-iteraciones` | `3` | Iteraciones máximas del bucle |
|
|
1047
|
+
| `--validar` | on | Valida la sintaxis del código antes de guardar en el editor propio |
|
|
1048
|
+
| `--no-validar-sintaxis` | off | Desactiva la validación de sintaxis del editor propio |
|
|
1049
|
+
| `--max-intentos-validacion` | `3` | Intentos de validación de sintaxis antes de cancelar la edición |
|
|
1050
|
+
|
|
1051
|
+
> **Nota:** el flag negativo de validación de sintaxis se llama `--no-validar-sintaxis`
|
|
1052
|
+
> porque `--no-validar` ya existe como alias de `--iniciar-proyecto`.
|
|
1016
1053
|
|
|
1017
1054
|
---
|
|
1018
1055
|
|
|
@@ -56,7 +56,7 @@ import unicodedata
|
|
|
56
56
|
import warnings
|
|
57
57
|
import webbrowser
|
|
58
58
|
from pathlib import Path
|
|
59
|
-
from typing import Any, List, Optional, Union
|
|
59
|
+
from typing import Any, List, Optional, Tuple, Union
|
|
60
60
|
from urllib.parse import urlparse
|
|
61
61
|
|
|
62
62
|
# `google-generativeai` es la dependencia del proveedor por defecto (Gemini).
|
|
@@ -111,7 +111,7 @@ except ImportError: # pragma: no cover
|
|
|
111
111
|
import concurrent.futures
|
|
112
112
|
from concurrent.futures import ThreadPoolExecutor
|
|
113
113
|
|
|
114
|
-
VERSION = "3.
|
|
114
|
+
VERSION = "3.4.0"
|
|
115
115
|
|
|
116
116
|
# v3.1.0 — Claves de API reconocidas para el modo por defecto (offline).
|
|
117
117
|
CLAVES_API_CONOCIDAS = (
|
|
@@ -301,6 +301,7 @@ MAX_ARCHIVOS_DEFECTO = 3 # archivos que recibe Aider
|
|
|
301
301
|
MAX_CANDIDATOS_DEFECTO = 80 # candidatos que se envían al selector IA
|
|
302
302
|
MAX_ITERACIONES_TEST_DEFECTO = 3
|
|
303
303
|
COMANDO_TEST_DEFECTO = "flutter test"
|
|
304
|
+
MAX_INTENTOS_VALIDACION = 3 # reintentos de validación del editor propio
|
|
304
305
|
|
|
305
306
|
# ---------------------------------------------------------------------------
|
|
306
307
|
# Proveedores de IA para la selección de archivos
|
|
@@ -1732,6 +1733,127 @@ def _editor_sobrescribir(archivo: str, contenido: str,
|
|
|
1732
1733
|
return False
|
|
1733
1734
|
|
|
1734
1735
|
|
|
1736
|
+
def _comandos_validacion(lenguaje: str, archivo_tmp: str) -> List[List[str]]:
|
|
1737
|
+
"""Comandos de validación sintáctica para ``lenguaje``.
|
|
1738
|
+
|
|
1739
|
+
Devuelve una lista de candidatos (cada uno un argv con ``archivo_tmp`` ya
|
|
1740
|
+
resuelto). Cuando un lenguaje admite un validador de reserva (p. ej.
|
|
1741
|
+
``dart analyze`` → ``dart format``), se incluyen en orden de preferencia.
|
|
1742
|
+
Devuelve ``[]`` si no existe validador para el lenguaje.
|
|
1743
|
+
"""
|
|
1744
|
+
if not lenguaje:
|
|
1745
|
+
return []
|
|
1746
|
+
if lenguaje in ("python", "py", "python3"):
|
|
1747
|
+
return [[sys.executable, "-m", "py_compile", archivo_tmp]]
|
|
1748
|
+
if lenguaje in ("javascript", "typescript", "tsx", "js", "ts", "node",
|
|
1749
|
+
"jsx", "mjs", "cjs", "mts", "cts"):
|
|
1750
|
+
return [["node", "--check", archivo_tmp]]
|
|
1751
|
+
if lenguaje == "dart":
|
|
1752
|
+
return [["dart", "analyze", archivo_tmp],
|
|
1753
|
+
["dart", "format", "--output=none", archivo_tmp]]
|
|
1754
|
+
if lenguaje == "go":
|
|
1755
|
+
return [["go", "build", "-n", archivo_tmp],
|
|
1756
|
+
["gofmt", "-e", archivo_tmp]]
|
|
1757
|
+
if lenguaje in ("rust", "rs"):
|
|
1758
|
+
return [["rustc", "--parse-only", archivo_tmp]]
|
|
1759
|
+
if lenguaje in ("java",):
|
|
1760
|
+
return [["javac", "-Xlint:none", archivo_tmp]]
|
|
1761
|
+
if lenguaje in ("c", "h"):
|
|
1762
|
+
return [["gcc", "-fsyntax-only", archivo_tmp],
|
|
1763
|
+
["clang", "-fsyntax-only", archivo_tmp]]
|
|
1764
|
+
if lenguaje in ("cpp", "cc", "cxx", "hpp", "hh", "hxx", "c++"):
|
|
1765
|
+
return [["g++", "-fsyntax-only", archivo_tmp],
|
|
1766
|
+
["clang++", "-fsyntax-only", archivo_tmp]]
|
|
1767
|
+
return []
|
|
1768
|
+
|
|
1769
|
+
|
|
1770
|
+
def _validar_sintaxis(archivo: str, contenido: str,
|
|
1771
|
+
directorio: str = ".") -> Tuple[bool, str]:
|
|
1772
|
+
"""Valida la sintaxis de ``contenido`` como si fuese el de ``archivo``.
|
|
1773
|
+
|
|
1774
|
+
Escribe el ``contenido`` en un archivo temporal (siempre conserva la
|
|
1775
|
+
extensión del archivo original para que el parser/linter lo reconozca) y
|
|
1776
|
+
ejecuta el comando de validación correspondiente al lenguaje detectado con
|
|
1777
|
+
``_lenguaje_archivo``. Nunca toca el archivo original.
|
|
1778
|
+
|
|
1779
|
+
Devuelve ``(exito, mensaje_error)``:
|
|
1780
|
+
- ``(True, "")`` si la validación pasó, o si no hay validador / comando
|
|
1781
|
+
disponible (se omite la validación).
|
|
1782
|
+
- ``(False, mensaje)`` si el validador rechazó el contenido o hubo timeout.
|
|
1783
|
+
"""
|
|
1784
|
+
lenguaje = _lenguaje_archivo(archivo, contenido) or ""
|
|
1785
|
+
if not lenguaje:
|
|
1786
|
+
depurar(
|
|
1787
|
+
f"[validar-sintaxis] Sin validador para '{archivo}' (lenguaje "
|
|
1788
|
+
f"desconocido); se omite la validación."
|
|
1789
|
+
)
|
|
1790
|
+
return True, ""
|
|
1791
|
+
|
|
1792
|
+
suffix = Path(archivo).suffix or ".txt"
|
|
1793
|
+
archivo_tmp = ""
|
|
1794
|
+
try:
|
|
1795
|
+
fd, archivo_tmp = tempfile.mkstemp(
|
|
1796
|
+
suffix=suffix, prefix=".snapcontext_validacion_")
|
|
1797
|
+
with os.fdopen(fd, "w", encoding="utf-8") as fh:
|
|
1798
|
+
fh.write(contenido or "")
|
|
1799
|
+
|
|
1800
|
+
comandos = _comandos_validacion(lenguaje, archivo_tmp)
|
|
1801
|
+
if not comandos:
|
|
1802
|
+
depurar(
|
|
1803
|
+
f"[validar-sintaxis] Sin validador para '{lenguaje}'; "
|
|
1804
|
+
"se omite la validación."
|
|
1805
|
+
)
|
|
1806
|
+
return True, ""
|
|
1807
|
+
|
|
1808
|
+
for cmd in comandos:
|
|
1809
|
+
binario = cmd[0]
|
|
1810
|
+
if shutil.which(binario) is None:
|
|
1811
|
+
depurar(
|
|
1812
|
+
f"[validar-sintaxis] Comando '{binario}' no disponible; "
|
|
1813
|
+
"se busca alternativa."
|
|
1814
|
+
)
|
|
1815
|
+
continue
|
|
1816
|
+
try:
|
|
1817
|
+
proc = subprocess.run(
|
|
1818
|
+
cmd, capture_output=True, text=True, timeout=60,
|
|
1819
|
+
cwd=directorio or ".",
|
|
1820
|
+
)
|
|
1821
|
+
except subprocess.TimeoutExpired:
|
|
1822
|
+
error(
|
|
1823
|
+
f"[validar-sintaxis] El comando '{binario}' excedió el "
|
|
1824
|
+
f"tiempo límite al validar '{archivo}'."
|
|
1825
|
+
)
|
|
1826
|
+
return False, (
|
|
1827
|
+
f"el comando '{binario}' excedió el tiempo límite de validación"
|
|
1828
|
+
)
|
|
1829
|
+
except (OSError, ValueError):
|
|
1830
|
+
continue # no se pudo lanzar → probar candidato siguiente
|
|
1831
|
+
|
|
1832
|
+
if proc.returncode == 0:
|
|
1833
|
+
return True, ""
|
|
1834
|
+
# Código de salida != 0 ⇒ error sintáctico (o validador fallo).
|
|
1835
|
+
salida = (proc.stderr or "").strip()
|
|
1836
|
+
if not salida:
|
|
1837
|
+
salida = (proc.stdout or "").strip()
|
|
1838
|
+
mensaje = salida or (
|
|
1839
|
+
f"el validador '{binario}' falló (código {proc.returncode})"
|
|
1840
|
+
)
|
|
1841
|
+
return False, mensaje
|
|
1842
|
+
|
|
1843
|
+
# Ningún candidato disponible → se omite la validación.
|
|
1844
|
+
depurar(
|
|
1845
|
+
f"[validar-sintaxis] Ningún validador disponible para '{lenguaje}'; "
|
|
1846
|
+
"se omite la validación."
|
|
1847
|
+
)
|
|
1848
|
+
return True, ""
|
|
1849
|
+
finally:
|
|
1850
|
+
if archivo_tmp:
|
|
1851
|
+
try:
|
|
1852
|
+
os.unlink(archivo_tmp)
|
|
1853
|
+
except OSError:
|
|
1854
|
+
pass
|
|
1855
|
+
|
|
1856
|
+
|
|
1735
1857
|
def _generar_parche(original: str, nuevo: str, ruta_archivo: str) -> str:
|
|
1736
1858
|
"""Genera un parche unificado (unified diff) entre `original` y `nuevo`.
|
|
1737
1859
|
|
|
@@ -4082,6 +4204,9 @@ def _ejecutar_paso_plan(paso: dict, args: argparse.Namespace,
|
|
|
4082
4204
|
directorio=str(ruta_raiz),
|
|
4083
4205
|
modo_edicion=modo_ed,
|
|
4084
4206
|
modelo=getattr(args, "modelo", None),
|
|
4207
|
+
validar=getattr(args, "validar", True),
|
|
4208
|
+
max_intentos_validacion=getattr(
|
|
4209
|
+
args, "max_intentos_validacion", MAX_INTENTOS_VALIDACION),
|
|
4085
4210
|
)
|
|
4086
4211
|
return (todo_ok, f"EditorPropio sobre {len(seleccion)} archivo(s)")
|
|
4087
4212
|
|
|
@@ -6842,7 +6967,8 @@ def _pintar(texto: str, clave: str) -> str:
|
|
|
6842
6967
|
# Categorías en orden de aparición; cada opción se muestra una sola vez.
|
|
6843
6968
|
CATEGORIAS_AYUDA = (
|
|
6844
6969
|
("Modos de ejecución",
|
|
6845
|
-
("--plan", "--auto", "--editor", "--modo-edicion", "--
|
|
6970
|
+
("--plan", "--auto", "--editor", "--modo-edicion", "--validar", "--no-validar-sintaxis", "--max-intentos-validacion",
|
|
6971
|
+
"--chat", "--web", "--web-puerto", "--demo",
|
|
6846
6972
|
"--init", "--init-claude", "--historial", "--historial-limpiar",
|
|
6847
6973
|
"--diagnostico", "--reparar", "--bienvenida")),
|
|
6848
6974
|
("Selección de archivos",
|
|
@@ -7346,6 +7472,26 @@ def crear_parser() -> argparse.ArgumentParser:
|
|
|
7346
7472
|
"'sobrescribir' (sobrescritura completa del archivo) o 'ast' "
|
|
7347
7473
|
"(edición basada en el árbol sintáctico con fallback a sobrescritura).",
|
|
7348
7474
|
)
|
|
7475
|
+
parser.add_argument(
|
|
7476
|
+
"--validar", dest="validar", action="store_const", const=True,
|
|
7477
|
+
default=True,
|
|
7478
|
+
help="Valida la sintaxis del código antes de guardar en el editor propio "
|
|
7479
|
+
"(por defecto activado).",
|
|
7480
|
+
)
|
|
7481
|
+
parser.add_argument(
|
|
7482
|
+
"--no-validar-sintaxis", dest="validar", action="store_const",
|
|
7483
|
+
const=False,
|
|
7484
|
+
help="Desactiva la validación de sintaxis en el editor propio "
|
|
7485
|
+
"(comportamiento previo a v3.4.0). Nota: su nombre no es "
|
|
7486
|
+
"'--no-validar' porque ese alias ya está reservado por "
|
|
7487
|
+
"--iniciar-proyecto.",
|
|
7488
|
+
)
|
|
7489
|
+
parser.add_argument(
|
|
7490
|
+
"--max-intentos-validacion", type=int,
|
|
7491
|
+
default=MAX_INTENTOS_VALIDACION, metavar="N",
|
|
7492
|
+
help=f"Intentos máximos de validación de sintaxis antes de cancelar la "
|
|
7493
|
+
f"edición (por defecto: {MAX_INTENTOS_VALIDACION}).",
|
|
7494
|
+
)
|
|
7349
7495
|
# Aprendizaje autónomo / memoria avanzada (v3.0.0)
|
|
7350
7496
|
parser.add_argument(
|
|
7351
7497
|
"--daemon", action="store_true",
|
|
@@ -98,7 +98,7 @@ class TestFlagsEdicion(unittest.TestCase):
|
|
|
98
98
|
self.assertEqual(parser.parse_args(["--modo-edicion", "sobrescribir", "c"]).modo_edicion, "sobrescribir")
|
|
99
99
|
|
|
100
100
|
def test_version_es_2_1_0(self):
|
|
101
|
-
self.assertEqual(sc.VERSION, "3.
|
|
101
|
+
self.assertEqual(sc.VERSION, "3.4.0")
|
|
102
102
|
|
|
103
103
|
|
|
104
104
|
if __name__ == "__main__":
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
# -*- coding: utf-8 -*-
|
|
3
|
-
"""Tests del editor propio v3.3.0.
|
|
3
|
+
"""Tests del editor propio v3.3.0 (base) — validado bajo v3.4.0.
|
|
4
4
|
|
|
5
5
|
Cubre el refinamiento multi-lenguaje del AST, el manejo de conflictos en
|
|
6
6
|
parches (validación previa, resolución incremental) y la integración con
|
|
@@ -174,7 +174,7 @@ class TestFlagsEdicionAST(unittest.TestCase):
|
|
|
174
174
|
self.assertEqual(args2.modo_edicion, "auto")
|
|
175
175
|
|
|
176
176
|
def test_version_es_2_2_0(self):
|
|
177
|
-
self.assertEqual(sc.VERSION, "3.
|
|
177
|
+
self.assertEqual(sc.VERSION, "3.4.0")
|
|
178
178
|
|
|
179
179
|
|
|
180
180
|
if __name__ == "__main__":
|
|
@@ -84,7 +84,7 @@ class TestFlagsEditor(unittest.TestCase):
|
|
|
84
84
|
self.assertIsInstance(orch.agente_editor_propio, AgenteEditorPropio)
|
|
85
85
|
|
|
86
86
|
def test_version_es_2_0_0(self):
|
|
87
|
-
self.assertEqual(sc.VERSION, "3.
|
|
87
|
+
self.assertEqual(sc.VERSION, "3.4.0")
|
|
88
88
|
|
|
89
89
|
|
|
90
90
|
if __name__ == "__main__":
|
|
@@ -17,7 +17,7 @@ import snapcontext as sc # noqa: E402
|
|
|
17
17
|
|
|
18
18
|
class TestVersion(unittest.TestCase):
|
|
19
19
|
def test_version_170_coherente(self):
|
|
20
|
-
self.assertEqual(sc.VERSION, "3.
|
|
20
|
+
self.assertEqual(sc.VERSION, "3.4.0")
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
class TestEstructuraJetBrains(unittest.TestCase):
|
|
@@ -248,7 +248,7 @@ class TestChatComandosGrafo(unittest.TestCase):
|
|
|
248
248
|
|
|
249
249
|
class TestVersion(unittest.TestCase):
|
|
250
250
|
def test_version_140_coherente(self):
|
|
251
|
-
self.assertEqual(sc.VERSION, "3.
|
|
251
|
+
self.assertEqual(sc.VERSION, "3.4.0")
|
|
252
252
|
|
|
253
253
|
|
|
254
254
|
if __name__ == "__main__":
|
|
@@ -182,7 +182,7 @@ class TestFlagsConfirmarCLI(unittest.TestCase):
|
|
|
182
182
|
self.assertFalse(self._parse(["--chat", "--no-confirmar"]).confirmar)
|
|
183
183
|
|
|
184
184
|
def test_version_es_1_2_0(self):
|
|
185
|
-
self.assertEqual(sc.VERSION, "3.
|
|
185
|
+
self.assertEqual(sc.VERSION, "3.4.0")
|
|
186
186
|
|
|
187
187
|
|
|
188
188
|
if __name__ == "__main__":
|
|
@@ -269,7 +269,7 @@ class TestFlagsPlanCLI(unittest.TestCase):
|
|
|
269
269
|
self.assertIsNone(self._parse(["--plan", "x"]).branch)
|
|
270
270
|
|
|
271
271
|
def test_version_es_1_2_0(self):
|
|
272
|
-
self.assertEqual(sc.VERSION, "3.
|
|
272
|
+
self.assertEqual(sc.VERSION, "3.4.0")
|
|
273
273
|
|
|
274
274
|
|
|
275
275
|
if __name__ == "__main__":
|
|
@@ -210,7 +210,7 @@ class TestEjecutarToolExecuteCommand(unittest.TestCase):
|
|
|
210
210
|
|
|
211
211
|
class TestVersionYFlags(unittest.TestCase):
|
|
212
212
|
def test_version_230(self):
|
|
213
|
-
self.assertEqual(sc.VERSION, "3.
|
|
213
|
+
self.assertEqual(sc.VERSION, "3.4.0")
|
|
214
214
|
|
|
215
215
|
def test_flag_paralelo(self):
|
|
216
216
|
args = sc.crear_parser().parse_args(["--plan", "t", "--paralelo", "3"])
|
|
@@ -168,7 +168,7 @@ class TestFlagsCLI(unittest.TestCase):
|
|
|
168
168
|
self.assertTrue(args.vista_previa)
|
|
169
169
|
|
|
170
170
|
def test_version_es_1_2_0(self):
|
|
171
|
-
self.assertEqual(sc.VERSION, "3.
|
|
171
|
+
self.assertEqual(sc.VERSION, "3.4.0")
|
|
172
172
|
|
|
173
173
|
|
|
174
174
|
class TestComandosAgenteChat(unittest.TestCase):
|
|
@@ -126,7 +126,7 @@ class TestIniciarProyecto(unittest.TestCase):
|
|
|
126
126
|
class TestVersion(unittest.TestCase):
|
|
127
127
|
def test_version_130_coherente(self):
|
|
128
128
|
import snapcontext as sc
|
|
129
|
-
self.assertEqual(sc.VERSION, "3.
|
|
129
|
+
self.assertEqual(sc.VERSION, "3.4.0")
|
|
130
130
|
|
|
131
131
|
|
|
132
132
|
if __name__ == "__main__":
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Tests de la validación de sintaxis del Editor Propio (v3.4.0).
|
|
4
|
+
|
|
5
|
+
Cubre ``_validar_sintaxis`` (con mocks de subprocess para distintos lenguajes),
|
|
6
|
+
los flags ``--validar / --no-validar / --max-intentos-validacion``, el flujo de
|
|
7
|
+
reintentos de ``_aplicar_modo_sobrescribir`` y la vista previa de parches.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import subprocess
|
|
11
|
+
import shutil
|
|
12
|
+
import tempfile
|
|
13
|
+
import unittest
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from types import SimpleNamespace
|
|
16
|
+
from unittest import mock
|
|
17
|
+
|
|
18
|
+
import snapcontext as sc
|
|
19
|
+
from agentes import AgenteEditorPropio
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class BaseValidacion(unittest.TestCase):
|
|
23
|
+
def setUp(self):
|
|
24
|
+
self.tmp = tempfile.mkdtemp(prefix="sc340_")
|
|
25
|
+
self.raiz = Path(self.tmp).resolve()
|
|
26
|
+
|
|
27
|
+
def tearDown(self):
|
|
28
|
+
shutil.rmtree(self.tmp, ignore_errors=True)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _proc(codigo, stderr="", stdout=""):
|
|
32
|
+
return SimpleNamespace(returncode=codigo, stderr=stderr, stdout=stdout)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class TestValidarSintaxis(BaseValidacion):
|
|
36
|
+
"""_validar_sintaxis con mocks de lenguaje y subprocess."""
|
|
37
|
+
|
|
38
|
+
def test_python_valido_devuelve_true(self):
|
|
39
|
+
with mock.patch.object(sc, "_lenguaje_archivo", return_value="python"), \
|
|
40
|
+
mock.patch("snapcontext.shutil.which", return_value="/a/python"), \
|
|
41
|
+
mock.patch("snapcontext.subprocess.run", return_value=_proc(0)) as run:
|
|
42
|
+
ok, mensaje = sc._validar_sintaxis("modulo.py", "def f(): pass\n", self.tmp)
|
|
43
|
+
self.assertTrue(ok)
|
|
44
|
+
self.assertEqual(mensaje, "")
|
|
45
|
+
cmd = run.call_args[0][0]
|
|
46
|
+
self.assertEqual(cmd[1:3], ["-m", "py_compile"])
|
|
47
|
+
|
|
48
|
+
def test_lenguaje_sin_validador_omite(self):
|
|
49
|
+
with mock.patch.object(sc, "_lenguaje_archivo", return_value="json"):
|
|
50
|
+
ok, mensaje = sc._validar_sintaxis("datos.json", "{}", self.tmp)
|
|
51
|
+
self.assertTrue(ok)
|
|
52
|
+
self.assertEqual(mensaje, "")
|
|
53
|
+
|
|
54
|
+
def test_comando_no_disponible_omite(self):
|
|
55
|
+
with mock.patch.object(sc, "_lenguaje_archivo", return_value="dart"), \
|
|
56
|
+
mock.patch("snapcontext.shutil.which", return_value=None), \
|
|
57
|
+
mock.patch("snapcontext.subprocess.run") as run:
|
|
58
|
+
ok, mensaje = sc._validar_sintaxis("app.dart", "void main(){}\n", self.tmp)
|
|
59
|
+
self.assertTrue(ok)
|
|
60
|
+
self.assertEqual(mensaje, "")
|
|
61
|
+
run.assert_not_called()
|
|
62
|
+
|
|
63
|
+
def test_dart_fallback_analyze_indisponible(self):
|
|
64
|
+
with mock.patch.object(sc, "_lenguaje_archivo", return_value="dart"), \
|
|
65
|
+
mock.patch("snapcontext.shutil.which",
|
|
66
|
+
side_effect=lambda b: b if b == "dart" else None), \
|
|
67
|
+
mock.patch("snapcontext.subprocess.run",
|
|
68
|
+
side_effect=[FileNotFoundError, _proc(0)]) as run:
|
|
69
|
+
ok, mensaje = sc._validar_sintaxis("lib/main.dart", "dart", self.tmp)
|
|
70
|
+
self.assertTrue(ok)
|
|
71
|
+
self.assertEqual(run.call_count, 2)
|
|
72
|
+
self.assertEqual(run.call_args_list[1][0][0][0:2],
|
|
73
|
+
["dart", "format"])
|
|
74
|
+
|
|
75
|
+
def test_timeout_devuelve_false(self):
|
|
76
|
+
exc = subprocess.TimeoutExpired("cmd", 60)
|
|
77
|
+
with mock.patch.object(sc, "_lenguaje_archivo", return_value="python"), \
|
|
78
|
+
mock.patch("snapcontext.shutil.which", return_value="/a/python"), \
|
|
79
|
+
mock.patch("snapcontext.subprocess.run", side_effect=exc):
|
|
80
|
+
ok, mensaje = sc._validar_sintaxis("modulo.py", "x = 1\n", self.tmp)
|
|
81
|
+
self.assertFalse(ok)
|
|
82
|
+
self.assertIn("tiempo", mensaje.lower())
|
|
83
|
+
|
|
84
|
+
def test_java_y_node_comandos(self):
|
|
85
|
+
with mock.patch.object(sc, "_lenguaje_archivo", return_value="java"), \
|
|
86
|
+
mock.patch("snapcontext.shutil.which", return_value="/a/javac"), \
|
|
87
|
+
mock.patch("snapcontext.subprocess.run", return_value=_proc(0)) as run:
|
|
88
|
+
ok, _ = sc._validar_sintaxis("Main.java", "class Main {}", self.tmp)
|
|
89
|
+
self.assertTrue(ok)
|
|
90
|
+
self.assertEqual(run.call_args[0][0][0], "javac")
|
|
91
|
+
|
|
92
|
+
with mock.patch.object(sc, "_lenguaje_archivo", return_value="javascript"), \
|
|
93
|
+
mock.patch("snapcontext.shutil.which", return_value="/a/node"), \
|
|
94
|
+
mock.patch("snapcontext.subprocess.run", return_value=_proc(0)) as run:
|
|
95
|
+
ok, _ = sc._validar_sintaxis("script.js", "const x = 1;", self.tmp)
|
|
96
|
+
self.assertTrue(ok)
|
|
97
|
+
self.assertEqual(run.call_args[0][0][0], "node")
|
|
98
|
+
|
|
99
|
+
def test_python_invalido_devuelve_false_y_mensaje(self):
|
|
100
|
+
with mock.patch.object(sc, "_lenguaje_archivo", return_value="python"), \
|
|
101
|
+
mock.patch("snapcontext.shutil.which", return_value="/a/python"), \
|
|
102
|
+
mock.patch("snapcontext.subprocess.run",
|
|
103
|
+
return_value=_proc(1, stderr="SyntaxError: invalid syntax")):
|
|
104
|
+
ok, mensaje = sc._validar_sintaxis("modulo.py", "def (\n", self.tmp)
|
|
105
|
+
self.assertFalse(ok)
|
|
106
|
+
self.assertIn("SyntaxError", mensaje)
|
|
107
|
+
class TestFlagsValidacion(unittest.TestCase):
|
|
108
|
+
def test_validar_por_defecto_true(self):
|
|
109
|
+
args = sc.crear_parser().parse_args(["consulta"])
|
|
110
|
+
self.assertTrue(args.validar)
|
|
111
|
+
|
|
112
|
+
def test_no_validar_sintaxis_false(self):
|
|
113
|
+
args = sc.crear_parser().parse_args(["consulta", "--no-validar-sintaxis"])
|
|
114
|
+
self.assertFalse(args.validar)
|
|
115
|
+
|
|
116
|
+
def test_validar_explicito_true(self):
|
|
117
|
+
args = sc.crear_parser().parse_args(["consulta", "--validar"])
|
|
118
|
+
self.assertTrue(args.validar)
|
|
119
|
+
|
|
120
|
+
def test_max_intentos_validacion_defecto_y_personalizado(self):
|
|
121
|
+
args = sc.crear_parser().parse_args(["consulta"])
|
|
122
|
+
self.assertEqual(args.max_intentos_validacion, 3)
|
|
123
|
+
args2 = sc.crear_parser().parse_args(
|
|
124
|
+
["consulta", "--max-intentos-validacion", "5"])
|
|
125
|
+
self.assertEqual(args2.max_intentos_validacion, 5)
|
|
126
|
+
class TestReintentosSobrescribir(BaseValidacion):
|
|
127
|
+
"""Flujo de reintentos de validación en _aplicar_modo_sobrescribir."""
|
|
128
|
+
|
|
129
|
+
def test_falla_y_luego_aprueba(self):
|
|
130
|
+
agente = AgenteEditorPropio()
|
|
131
|
+
archivo = "modulo.py"
|
|
132
|
+
(self.raiz / archivo).write_text("def f(): return 1\n", encoding="utf-8")
|
|
133
|
+
with mock.patch.object(sc, "cargar_configuracion", return_value={}), \
|
|
134
|
+
mock.patch.object(sc, "_enviar_al_proveedor",
|
|
135
|
+
return_value="def f(): return 2\n") as enviar, \
|
|
136
|
+
mock.patch.object(sc, "_validar_sintaxis",
|
|
137
|
+
side_effect=[(False, "SyntaxError"), (True, "")]) as val, \
|
|
138
|
+
mock.patch.object(agente, "sobrescribir", return_value=True) as sob:
|
|
139
|
+
ok = agente._aplicar_modo_sobrescribir(
|
|
140
|
+
archivo, "cambiar retorno", "def f(): return 1\n",
|
|
141
|
+
modelo=None, directorio=str(self.raiz))
|
|
142
|
+
self.assertTrue(ok)
|
|
143
|
+
self.assertEqual(enviar.call_count, 2) # 1ª generación + reintento
|
|
144
|
+
self.assertEqual(val.call_count, 2)
|
|
145
|
+
sob.assert_called_once_with(archivo, "def f(): return 2\n",
|
|
146
|
+
str(self.raiz))
|
|
147
|
+
|
|
148
|
+
def test_agota_intentos_cancela_sin_guardar(self):
|
|
149
|
+
agente = AgenteEditorPropio()
|
|
150
|
+
archivo = "modulo.py"
|
|
151
|
+
(self.raiz / archivo).write_text("x = 1\n", encoding="utf-8")
|
|
152
|
+
with mock.patch.object(sc, "cargar_configuracion", return_value={}), \
|
|
153
|
+
mock.patch.object(sc, "_enviar_al_proveedor",
|
|
154
|
+
return_value="malo()(") as enviar, \
|
|
155
|
+
mock.patch.object(sc, "_validar_sintaxis",
|
|
156
|
+
return_value=(False, "SyntaxError")) as val, \
|
|
157
|
+
mock.patch.object(agente, "sobrescribir") as sob:
|
|
158
|
+
ok = agente._aplicar_modo_sobrescribir(
|
|
159
|
+
archivo, "cambia", "x = 1\n", modelo=None,
|
|
160
|
+
directorio=self.tmp, max_intentos_validacion=3)
|
|
161
|
+
self.assertFalse(ok)
|
|
162
|
+
self.assertEqual(enviar.call_count, 3)
|
|
163
|
+
self.assertEqual(val.call_count, 3)
|
|
164
|
+
sob.assert_not_called()
|
|
165
|
+
|
|
166
|
+
def test_validar_false_no_valida(self):
|
|
167
|
+
agente = AgenteEditorPropio()
|
|
168
|
+
archivo = "modulo.py"
|
|
169
|
+
with mock.patch.object(sc, "cargar_configuracion", return_value={}), \
|
|
170
|
+
mock.patch.object(sc, "_enviar_al_proveedor",
|
|
171
|
+
return_value="x = 1\n") as enviar, \
|
|
172
|
+
mock.patch.object(sc, "_validar_sintaxis") as val, \
|
|
173
|
+
mock.patch.object(agente, "sobrescribir", return_value=True) as sob:
|
|
174
|
+
ok = agente._aplicar_modo_sobrescribir(
|
|
175
|
+
archivo, "cambiar", "", modelo=None, directorio=self.tmp,
|
|
176
|
+
validar=False)
|
|
177
|
+
self.assertTrue(ok)
|
|
178
|
+
self.assertEqual(enviar.call_count, 1)
|
|
179
|
+
val.assert_not_called()
|
|
180
|
+
sob.assert_called_once()
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
class TestPreviewParche(BaseValidacion):
|
|
184
|
+
def test_preview_aplica_hunk(self):
|
|
185
|
+
agente = AgenteEditorPropio()
|
|
186
|
+
parche = ("--- a/modulo.py\n+++ b/modulo.py\n@@ -1 +1 @@\n"
|
|
187
|
+
"-def fn(): return 1\n+def fn(): return 2\n")
|
|
188
|
+
resultado, aplicados = agente._aplicar_parche_preview(
|
|
189
|
+
parche, "def fn(): return 1\n")
|
|
190
|
+
self.assertEqual(aplicados, 1)
|
|
191
|
+
self.assertIn("def fn(): return 2", resultado)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
if __name__ == "__main__":
|
|
195
|
+
unittest.main()
|
|
@@ -17,7 +17,7 @@ INDEX = RAIZ / "web" / "static" / "index.html"
|
|
|
17
17
|
|
|
18
18
|
class TestVersion(unittest.TestCase):
|
|
19
19
|
def test_version_160_coherente(self):
|
|
20
|
-
self.assertEqual(sc.VERSION, "3.
|
|
20
|
+
self.assertEqual(sc.VERSION, "3.4.0")
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
class TestFrontendV160(unittest.TestCase):
|
|
File without changes
|
|
File without changes
|
{snapcontext-3.3.0 → snapcontext-3.4.0}/snapcontext.egg-info/__pycache__/top_level.cpython-313.pyc
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|