snapcontext 3.2.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.
Files changed (45) hide show
  1. {snapcontext-3.2.0/snapcontext.egg-info → snapcontext-3.4.0}/PKG-INFO +51 -2
  2. {snapcontext-3.2.0 → snapcontext-3.4.0}/README.md +50 -1
  3. {snapcontext-3.2.0 → snapcontext-3.4.0}/agentes.py +214 -38
  4. {snapcontext-3.2.0 → snapcontext-3.4.0}/orquestador.py +3 -0
  5. {snapcontext-3.2.0 → snapcontext-3.4.0}/pyproject.toml +1 -1
  6. {snapcontext-3.2.0 → snapcontext-3.4.0/snapcontext.egg-info}/PKG-INFO +51 -2
  7. {snapcontext-3.2.0 → snapcontext-3.4.0}/snapcontext.egg-info/SOURCES.txt +2 -0
  8. {snapcontext-3.2.0 → snapcontext-3.4.0}/snapcontext.py +514 -14
  9. {snapcontext-3.2.0 → snapcontext-3.4.0}/tests/test_auto_017.py +1 -1
  10. {snapcontext-3.2.0 → snapcontext-3.4.0}/tests/test_diagnostico_310.py +1 -1
  11. {snapcontext-3.2.0 → snapcontext-3.4.0}/tests/test_diff_editor_210.py +3 -1
  12. snapcontext-3.4.0/tests/test_editor_330.py +347 -0
  13. {snapcontext-3.2.0 → snapcontext-3.4.0}/tests/test_editor_ast_220.py +1 -1
  14. {snapcontext-3.2.0 → snapcontext-3.4.0}/tests/test_editor_propio_200.py +1 -1
  15. {snapcontext-3.2.0 → snapcontext-3.4.0}/tests/test_exe_150.py +1 -1
  16. {snapcontext-3.2.0 → snapcontext-3.4.0}/tests/test_jetbrains_170.py +1 -1
  17. {snapcontext-3.2.0 → snapcontext-3.4.0}/tests/test_mcp_014.py +1 -1
  18. {snapcontext-3.2.0 → snapcontext-3.4.0}/tests/test_mcp_140.py +1 -1
  19. {snapcontext-3.2.0 → snapcontext-3.4.0}/tests/test_memoria_015.py +1 -1
  20. {snapcontext-3.2.0 → snapcontext-3.4.0}/tests/test_memoria_300.py +1 -1
  21. {snapcontext-3.2.0 → snapcontext-3.4.0}/tests/test_permisos_013.py +1 -1
  22. {snapcontext-3.2.0 → snapcontext-3.4.0}/tests/test_plan_012.py +1 -1
  23. {snapcontext-3.2.0 → snapcontext-3.4.0}/tests/test_plan_mcp_230.py +1 -1
  24. {snapcontext-3.2.0 → snapcontext-3.4.0}/tests/test_v010.py +1 -1
  25. {snapcontext-3.2.0 → snapcontext-3.4.0}/tests/test_validacion_130.py +1 -1
  26. snapcontext-3.4.0/tests/test_validacion_sintaxis_340.py +195 -0
  27. {snapcontext-3.2.0 → snapcontext-3.4.0}/tests/test_vscode_016.py +1 -1
  28. {snapcontext-3.2.0 → snapcontext-3.4.0}/tests/test_web_160.py +1 -1
  29. {snapcontext-3.2.0 → snapcontext-3.4.0}/LICENSE +0 -0
  30. {snapcontext-3.2.0 → snapcontext-3.4.0}/setup.cfg +0 -0
  31. {snapcontext-3.2.0 → snapcontext-3.4.0}/snapcontext.egg-info/__pycache__/top_level.cpython-313.pyc +0 -0
  32. {snapcontext-3.2.0 → snapcontext-3.4.0}/snapcontext.egg-info/dependency_links.txt +0 -0
  33. {snapcontext-3.2.0 → snapcontext-3.4.0}/snapcontext.egg-info/entry_points.txt +0 -0
  34. {snapcontext-3.2.0 → snapcontext-3.4.0}/snapcontext.egg-info/requires.txt +0 -0
  35. {snapcontext-3.2.0 → snapcontext-3.4.0}/snapcontext.egg-info/top_level.txt +0 -0
  36. {snapcontext-3.2.0 → snapcontext-3.4.0}/tests/test_agentes.py +0 -0
  37. {snapcontext-3.2.0 → snapcontext-3.4.0}/tests/test_bienvenida_311.py +0 -0
  38. {snapcontext-3.2.0 → snapcontext-3.4.0}/tests/test_deteccion.py +0 -0
  39. {snapcontext-3.2.0 → snapcontext-3.4.0}/tests/test_embeddings_110.py +0 -0
  40. {snapcontext-3.2.0 → snapcontext-3.4.0}/tests/test_orquestador.py +0 -0
  41. {snapcontext-3.2.0 → snapcontext-3.4.0}/tests/test_vscode_ts_320.py +0 -0
  42. {snapcontext-3.2.0 → snapcontext-3.4.0}/tests/test_web_120.py +0 -0
  43. {snapcontext-3.2.0 → snapcontext-3.4.0}/web/__init__.py +0 -0
  44. {snapcontext-3.2.0 → snapcontext-3.4.0}/web/app.py +0 -0
  45. {snapcontext-3.2.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.2.0
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
- ![v2.1.0](https://img.shields.io/badge/version-2.1.0-blue.svg)
48
+ ![v3.4.0](https://img.shields.io/badge/version-3.4.0-blue.svg)
49
49
  [![PyPI](https://badge.fury.io/py/snapcontext.svg)](https://pypi.org/project/snapcontext/)
50
50
  [![CI](https://img.shields.io/github/actions/workflow/status/NicolasBruna24/snapcontext/ci.yml?branch=main&label=tests)](https://github.com/NicolasBruña24/snapcontext/actions)
51
51
  ![Python 3.9+](https://img.shields.io/badge/python-3.9%2B-blue.svg)
@@ -143,6 +143,18 @@ La extensión vive en `vscode/` y está escrita en TypeScript:
143
143
  - Empaquetar: `vscode/scripts/empaquetar.ps1` / `.sh` (compilan antes de
144
144
  ejecutar `vsce package`).
145
145
 
146
+ ### Editor propio refinado (v3.3.0)
147
+ - **Detección de lenguaje robusta**: extensión + heurística por contenido
148
+ (shebang, patrones de código) para proyectos mixtos.
149
+ - **Manejo de conflictos en parches**: validación previa del contenido del
150
+ archivo y resolución automática línea a línea si `git apply`/`patch`
151
+ fallan (con aviso de aplicación parcial); ya no se sobrescribe a ciegas.
152
+ - **Prompt de edición mejorado**: lenguaje, tamaño, resumen AST con
153
+ posiciones y reglas de precisión (estilo conservado, cambios mínimos).
154
+ - **Aprendizaje de patrones**: las ediciones exitosas se guardan como skills
155
+ (`editor-<patrón>`) y su estrategia se reutiliza automáticamente en
156
+ tareas similares.
157
+
146
158
  ## 🚀 Inicio rápido (5 minutos)
147
159
 
148
160
  ```bash
@@ -187,6 +199,37 @@ snapcontext "corregir tipado en login" --editor propio --modo-edicion parche
187
199
 
188
200
  # Refactorización estructural con AST
189
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
190
233
  ```
191
234
 
192
235
  ## 🧭 Modos y alias
@@ -1001,6 +1044,12 @@ locales y te deja elegir con las flechas:
1001
1044
  | `--url-defecto` | `http://localhost:5000` | URL para abrir el navegador si Flutter no reporta una |
1002
1045
  | `--comando-test` | `"flutter test"` | Comando del bucle de pruebas |
1003
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`.
1004
1053
 
1005
1054
  ---
1006
1055
 
@@ -1,6 +1,6 @@
1
1
  # SnapContext
2
2
 
3
- ![v2.1.0](https://img.shields.io/badge/version-2.1.0-blue.svg)
3
+ ![v3.4.0](https://img.shields.io/badge/version-3.4.0-blue.svg)
4
4
  [![PyPI](https://badge.fury.io/py/snapcontext.svg)](https://pypi.org/project/snapcontext/)
5
5
  [![CI](https://img.shields.io/github/actions/workflow/status/NicolasBruna24/snapcontext/ci.yml?branch=main&label=tests)](https://github.com/NicolasBruña24/snapcontext/actions)
6
6
  ![Python 3.9+](https://img.shields.io/badge/python-3.9%2B-blue.svg)
@@ -98,6 +98,18 @@ La extensión vive en `vscode/` y está escrita en TypeScript:
98
98
  - Empaquetar: `vscode/scripts/empaquetar.ps1` / `.sh` (compilan antes de
99
99
  ejecutar `vsce package`).
100
100
 
101
+ ### Editor propio refinado (v3.3.0)
102
+ - **Detección de lenguaje robusta**: extensión + heurística por contenido
103
+ (shebang, patrones de código) para proyectos mixtos.
104
+ - **Manejo de conflictos en parches**: validación previa del contenido del
105
+ archivo y resolución automática línea a línea si `git apply`/`patch`
106
+ fallan (con aviso de aplicación parcial); ya no se sobrescribe a ciegas.
107
+ - **Prompt de edición mejorado**: lenguaje, tamaño, resumen AST con
108
+ posiciones y reglas de precisión (estilo conservado, cambios mínimos).
109
+ - **Aprendizaje de patrones**: las ediciones exitosas se guardan como skills
110
+ (`editor-<patrón>`) y su estrategia se reutiliza automáticamente en
111
+ tareas similares.
112
+
101
113
  ## 🚀 Inicio rápido (5 minutos)
102
114
 
103
115
  ```bash
@@ -142,6 +154,37 @@ snapcontext "corregir tipado en login" --editor propio --modo-edicion parche
142
154
 
143
155
  # Refactorización estructural con AST
144
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
145
188
  ```
146
189
 
147
190
  ## 🧭 Modos y alias
@@ -956,6 +999,12 @@ locales y te deja elegir con las flechas:
956
999
  | `--url-defecto` | `http://localhost:5000` | URL para abrir el navegador si Flutter no reporta una |
957
1000
  | `--comando-test` | `"flutter test"` | Comando del bucle de pruebas |
958
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`.
959
1008
 
960
1009
  ---
961
1010
 
@@ -181,15 +181,22 @@ class AgenteEditorPropio:
181
181
  self,
182
182
  parche: str,
183
183
  directorio: str = ".",
184
+ contenido_esperado: Optional[str] = None,
184
185
  ) -> bool:
185
- """Aplica un parche unificado en ``directorio``.
186
+ """Aplica un parche unificado en ``directorio`` (v3.3.0).
187
+
188
+ Con ``contenido_esperado`` se valida antes que el archivo coincida
189
+ con el usado para generar el parche y, si hay conflicto, se intenta
190
+ la resolución incremental línea a línea.
186
191
 
187
192
  Devuelve ``True`` si se aplicó con éxito, ``False`` si falló.
188
193
  """
189
194
  import snapcontext as sc
190
195
 
191
196
  sc.depurar(f"[AgenteEditorPropio] Aplicando parche en '{directorio}'")
192
- return sc._aplicar_parche(parche, directorio=directorio)
197
+ return sc._aplicar_parche_con_resolucion(
198
+ parche, directorio=directorio,
199
+ contenido_esperado=contenido_esperado)
193
200
 
194
201
  def _cadena_modos(self, archivo: str, mensaje: str,
195
202
  modo_edicion: str) -> List[str]:
@@ -225,55 +232,191 @@ class AgenteEditorPropio:
225
232
  return sc._editor_ast(archivo, tarea, directorio=directorio,
226
233
  proveedor=proveedor, modelo=modelo)
227
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
+
228
280
  def _aplicar_modo_parche(self, archivo: str, mensaje: str,
229
281
  contenido_actual: str, modelo: Optional[str],
230
- directorio: str) -> bool:
282
+ directorio: str, validar: bool = True,
283
+ max_intentos_validacion: int = 3) -> bool:
231
284
  """Intenta editar `archivo` pidiendo un parche unificado al proveedor."""
232
285
  import snapcontext as sc
233
286
 
234
287
  pref = sc.cargar_configuracion()
235
288
  proveedor = pref.get("provider") or sc.PROVEEDOR_DEFECTO
236
- prompt = (
237
- f"Genera un parche unificado (unified diff) que modifique el archivo para cumplir con la tarea. "
238
- f"El parche debe ser aplicable con 'patch -p1' o 'git apply' (encabezados --- a/{archivo} y +++ b/{archivo}).\n\n"
239
- f"Tarea: {mensaje}\n"
240
- f"Archivo: {archivo}\n\n"
241
- f"Contenido actual:\n```\n{contenido_actual}\n```\n\n"
242
- f"Devuelve ÚNICAMENTE el diff unificado, sin explicaciones ni rodeos."
243
- )
244
- respuesta = sc._enviar_al_proveedor(
245
- proveedor, modelo, [{"role": "user", "content": prompt}])
246
- diff_limpio = respuesta
247
- if "--- " in diff_limpio and "+++ " in diff_limpio:
248
- idx_inicio = diff_limpio.find("--- ")
249
- diff_limpio = diff_limpio[idx_inicio:]
250
- if "```" in diff_limpio:
251
- diff_limpio = diff_limpio[:diff_limpio.find("```")]
252
- if "--- " in diff_limpio and "+++ " in diff_limpio and "@@" in diff_limpio:
253
- return self.aplicar_parche(diff_limpio, directorio)
289
+ lenguaje = sc._lenguaje_archivo(archivo, contenido_actual) or "?"
290
+ num_lineas = (contenido_actual.count("\n") + 1
291
+ if contenido_actual else 0)
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
254
360
  return False
255
361
 
256
362
  def _aplicar_modo_sobrescribir(self, archivo: str, mensaje: str,
257
363
  contenido_actual: str, modelo: Optional[str],
258
- directorio: str) -> bool:
364
+ directorio: str, validar: bool = True,
365
+ max_intentos_validacion: int = 3) -> bool:
259
366
  """Sobrescribe `archivo` con el código completo que devuelve el proveedor."""
260
367
  import snapcontext as sc
261
368
 
262
369
  pref = sc.cargar_configuracion()
263
370
  proveedor = pref.get("provider") or sc.PROVEEDOR_DEFECTO
264
- prompt = (
265
- f"Modifica el siguiente archivo para cumplir con la tarea.\n\n"
266
- f"Tarea: {mensaje}\n"
267
- f"Archivo: {archivo}\n\n"
268
- f"Contenido actual:\n```\n{contenido_actual}\n```\n\n"
269
- f"Devuelve ÚNICAMENTE el código completo resultante, sin explicaciones ni markdown."
270
- )
271
- nuevo_contenido = sc._enviar_al_proveedor(
272
- proveedor, modelo, [{"role": "user", "content": prompt}])
273
- if nuevo_contenido.startswith("```"):
274
- lineas = nuevo_contenido.splitlines()
275
- if len(lineas) >= 2 and lineas[-1].startswith("```"):
276
- nuevo_contenido = "\n".join(lineas[1:-1])
371
+ lenguaje = sc._lenguaje_archivo(archivo, contenido_actual) or "?"
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
+
277
420
  return self.sobrescribir(archivo, nuevo_contenido, directorio)
278
421
 
279
422
  def ejecutar(
@@ -283,6 +426,8 @@ class AgenteEditorPropio:
283
426
  directorio: str = ".",
284
427
  modo_edicion: str = "auto",
285
428
  modelo: Optional[str] = None,
429
+ validar: bool = True,
430
+ max_intentos_validacion: int = 3,
286
431
  ) -> bool:
287
432
  """Aplica las modificaciones para `archivos` con el `mensaje` especificado.
288
433
 
@@ -298,6 +443,18 @@ class AgenteEditorPropio:
298
443
  raiz = Path(directorio).resolve()
299
444
  todo_ok = True
300
445
 
446
+ # v3.3.0 — Integración con skills: si ya existe un patrón de edición
447
+ # aprendido para esta tarea, se prioriza su estrategia (sin pasar por
448
+ # el proveedor de IA cuando el skill lo permita).
449
+ estrategia_aprendida = None
450
+ try:
451
+ estrategia_aprendida = sc._skill_editor_estrategia(mensaje)
452
+ if estrategia_aprendida:
453
+ sc.info("[skills] Reutilizando patrón de edición aprendido: "
454
+ f"'{estrategia_aprendida}'.")
455
+ except Exception as exc:
456
+ sc.depurar(f"[skills] Búsqueda de skill falló: {exc}")
457
+
301
458
  for arch in archivos:
302
459
  camino = (raiz / arch).resolve()
303
460
  contenido_actual = ""
@@ -309,6 +466,10 @@ class AgenteEditorPropio:
309
466
  pass
310
467
 
311
468
  cadena = self._cadena_modos(arch, mensaje, modo_edicion)
469
+ # El skill aprendido se coloca el primero en la cadena.
470
+ if estrategia_aprendida and estrategia_aprendida in cadena:
471
+ cadena.remove(estrategia_aprendida)
472
+ cadena.insert(0, estrategia_aprendida)
312
473
  conseguido = False
313
474
  for estrategia in cadena:
314
475
  try:
@@ -317,10 +478,14 @@ class AgenteEditorPropio:
317
478
  str(raiz), modelo)
318
479
  elif estrategia == "parche":
319
480
  conseguido = self._aplicar_modo_parche(
320
- 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)
321
484
  elif estrategia == "sobrescribir":
322
485
  conseguido = self._aplicar_modo_sobrescribir(
323
- 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)
324
489
  else:
325
490
  conseguido = False
326
491
  except Exception as exc:
@@ -329,7 +494,18 @@ class AgenteEditorPropio:
329
494
  conseguido = False
330
495
  if conseguido:
331
496
  break
332
- if not conseguido:
497
+
498
+ if conseguido:
499
+ # v3.3.0 — Aprendizaje: guardar/reforzar el patrón de edición.
500
+ patron = sc._editor_clasificar_tarea(mensaje)
501
+ sid = sc._skill_editor_guardar(
502
+ mensaje, arch, patron, estrategia=estrategia)
503
+ if sid is not None:
504
+ try:
505
+ sc._skill_registrar_exito(sid)
506
+ except Exception as exc:
507
+ sc.depurar(f"[skills] Refuerzo falló: {exc}")
508
+ else:
333
509
  sc.error(f"[AgenteEditorPropio] No se pudo editar '{arch}'.")
334
510
  todo_ok = False
335
511
 
@@ -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.2.0"
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.2.0
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
- ![v2.1.0](https://img.shields.io/badge/version-2.1.0-blue.svg)
48
+ ![v3.4.0](https://img.shields.io/badge/version-3.4.0-blue.svg)
49
49
  [![PyPI](https://badge.fury.io/py/snapcontext.svg)](https://pypi.org/project/snapcontext/)
50
50
  [![CI](https://img.shields.io/github/actions/workflow/status/NicolasBruna24/snapcontext/ci.yml?branch=main&label=tests)](https://github.com/NicolasBruña24/snapcontext/actions)
51
51
  ![Python 3.9+](https://img.shields.io/badge/python-3.9%2B-blue.svg)
@@ -143,6 +143,18 @@ La extensión vive en `vscode/` y está escrita en TypeScript:
143
143
  - Empaquetar: `vscode/scripts/empaquetar.ps1` / `.sh` (compilan antes de
144
144
  ejecutar `vsce package`).
145
145
 
146
+ ### Editor propio refinado (v3.3.0)
147
+ - **Detección de lenguaje robusta**: extensión + heurística por contenido
148
+ (shebang, patrones de código) para proyectos mixtos.
149
+ - **Manejo de conflictos en parches**: validación previa del contenido del
150
+ archivo y resolución automática línea a línea si `git apply`/`patch`
151
+ fallan (con aviso de aplicación parcial); ya no se sobrescribe a ciegas.
152
+ - **Prompt de edición mejorado**: lenguaje, tamaño, resumen AST con
153
+ posiciones y reglas de precisión (estilo conservado, cambios mínimos).
154
+ - **Aprendizaje de patrones**: las ediciones exitosas se guardan como skills
155
+ (`editor-<patrón>`) y su estrategia se reutiliza automáticamente en
156
+ tareas similares.
157
+
146
158
  ## 🚀 Inicio rápido (5 minutos)
147
159
 
148
160
  ```bash
@@ -187,6 +199,37 @@ snapcontext "corregir tipado en login" --editor propio --modo-edicion parche
187
199
 
188
200
  # Refactorización estructural con AST
189
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
190
233
  ```
191
234
 
192
235
  ## 🧭 Modos y alias
@@ -1001,6 +1044,12 @@ locales y te deja elegir con las flechas:
1001
1044
  | `--url-defecto` | `http://localhost:5000` | URL para abrir el navegador si Flutter no reporta una |
1002
1045
  | `--comando-test` | `"flutter test"` | Comando del bucle de pruebas |
1003
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`.
1004
1053
 
1005
1054
  ---
1006
1055
 
@@ -17,6 +17,7 @@ tests/test_bienvenida_311.py
17
17
  tests/test_deteccion.py
18
18
  tests/test_diagnostico_310.py
19
19
  tests/test_diff_editor_210.py
20
+ tests/test_editor_330.py
20
21
  tests/test_editor_ast_220.py
21
22
  tests/test_editor_propio_200.py
22
23
  tests/test_embeddings_110.py
@@ -32,6 +33,7 @@ tests/test_plan_012.py
32
33
  tests/test_plan_mcp_230.py
33
34
  tests/test_v010.py
34
35
  tests/test_validacion_130.py
36
+ tests/test_validacion_sintaxis_340.py
35
37
  tests/test_vscode_016.py
36
38
  tests/test_vscode_ts_320.py
37
39
  tests/test_web_120.py