snapcontext 2.1.0__tar.gz → 2.3.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 (39) hide show
  1. {snapcontext-2.1.0/snapcontext.egg-info → snapcontext-2.3.0}/PKG-INFO +48 -8
  2. {snapcontext-2.1.0 → snapcontext-2.3.0}/README.md +47 -7
  3. {snapcontext-2.1.0 → snapcontext-2.3.0}/agentes.py +165 -63
  4. {snapcontext-2.1.0 → snapcontext-2.3.0}/orquestador.py +3 -1
  5. {snapcontext-2.1.0 → snapcontext-2.3.0}/pyproject.toml +89 -89
  6. {snapcontext-2.1.0 → snapcontext-2.3.0/snapcontext.egg-info}/PKG-INFO +48 -8
  7. {snapcontext-2.1.0 → snapcontext-2.3.0}/snapcontext.egg-info/SOURCES.txt +2 -0
  8. {snapcontext-2.1.0 → snapcontext-2.3.0}/snapcontext.py +737 -31
  9. {snapcontext-2.1.0 → snapcontext-2.3.0}/tests/test_auto_017.py +190 -190
  10. {snapcontext-2.1.0 → snapcontext-2.3.0}/tests/test_diff_editor_210.py +103 -103
  11. snapcontext-2.3.0/tests/test_editor_ast_220.py +181 -0
  12. {snapcontext-2.1.0 → snapcontext-2.3.0}/tests/test_editor_propio_200.py +91 -91
  13. {snapcontext-2.1.0 → snapcontext-2.3.0}/tests/test_exe_150.py +72 -72
  14. {snapcontext-2.1.0 → snapcontext-2.3.0}/tests/test_jetbrains_170.py +115 -115
  15. {snapcontext-2.1.0 → snapcontext-2.3.0}/tests/test_mcp_014.py +225 -225
  16. {snapcontext-2.1.0 → snapcontext-2.3.0}/tests/test_mcp_140.py +255 -255
  17. {snapcontext-2.1.0 → snapcontext-2.3.0}/tests/test_memoria_015.py +186 -186
  18. {snapcontext-2.1.0 → snapcontext-2.3.0}/tests/test_permisos_013.py +190 -190
  19. {snapcontext-2.1.0 → snapcontext-2.3.0}/tests/test_plan_012.py +279 -279
  20. snapcontext-2.3.0/tests/test_plan_mcp_230.py +223 -0
  21. {snapcontext-2.1.0 → snapcontext-2.3.0}/tests/test_v010.py +248 -248
  22. {snapcontext-2.1.0 → snapcontext-2.3.0}/tests/test_validacion_130.py +133 -133
  23. {snapcontext-2.1.0 → snapcontext-2.3.0}/tests/test_vscode_016.py +94 -94
  24. {snapcontext-2.1.0 → snapcontext-2.3.0}/tests/test_web_160.py +95 -95
  25. {snapcontext-2.1.0 → snapcontext-2.3.0}/web/app.py +392 -392
  26. {snapcontext-2.1.0 → snapcontext-2.3.0}/LICENSE +0 -0
  27. {snapcontext-2.1.0 → snapcontext-2.3.0}/setup.cfg +0 -0
  28. {snapcontext-2.1.0 → snapcontext-2.3.0}/snapcontext.egg-info/__pycache__/top_level.cpython-313.pyc +0 -0
  29. {snapcontext-2.1.0 → snapcontext-2.3.0}/snapcontext.egg-info/dependency_links.txt +0 -0
  30. {snapcontext-2.1.0 → snapcontext-2.3.0}/snapcontext.egg-info/entry_points.txt +0 -0
  31. {snapcontext-2.1.0 → snapcontext-2.3.0}/snapcontext.egg-info/requires.txt +0 -0
  32. {snapcontext-2.1.0 → snapcontext-2.3.0}/snapcontext.egg-info/top_level.txt +0 -0
  33. {snapcontext-2.1.0 → snapcontext-2.3.0}/tests/test_agentes.py +0 -0
  34. {snapcontext-2.1.0 → snapcontext-2.3.0}/tests/test_deteccion.py +0 -0
  35. {snapcontext-2.1.0 → snapcontext-2.3.0}/tests/test_embeddings_110.py +0 -0
  36. {snapcontext-2.1.0 → snapcontext-2.3.0}/tests/test_orquestador.py +0 -0
  37. {snapcontext-2.1.0 → snapcontext-2.3.0}/tests/test_web_120.py +0 -0
  38. {snapcontext-2.1.0 → snapcontext-2.3.0}/web/__init__.py +0 -0
  39. {snapcontext-2.1.0 → snapcontext-2.3.0}/web/static/index.html +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: snapcontext
3
- Version: 2.1.0
3
+ Version: 2.3.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
@@ -104,12 +104,18 @@ snapcontext interactive # o snapcontext --web
104
104
  Auto-detección de proyecto (Flutter, Node/React, Python, Go, Rust…) que ajusta
105
105
  carpetas, extensiones y comandos de test por defecto.
106
106
 
107
- ## 🛠️ Editor Integrado Propio (v2.1.0)
107
+ ## 🛠️ Editor Integrado Propio (v2.2.0)
108
108
 
109
- SnapContext incluye su propio editor integrado con soporte de parches unificados:
109
+ SnapContext incluye su propio editor integrado con soporte de parches unificados
110
+ y edición basada en AST (Árbol Sintáctico) para refactorizaciones precisas:
110
111
  - **`--editor propio`**: Aplica cambios directamente y crea copias de seguridad automáticas en `~/.snapcontext/backups/`.
111
- - **`--modo-edicion {auto,parche,sobrescribir}`**:
112
- - `auto` (por defecto): Genera un parche unificado (`git apply` / `patch`) y hace fallback a sobrescritura si no es aplicable.
112
+ - **`--modo-edicion {auto,parche,sobrescribir,ast}`**:
113
+ - `auto` (por defecto): Genera un parche unificado (`git apply` / `patch`), pero
114
+ para refactorizaciones estructurales intenta primero la edición **AST** y cae a
115
+ sobrescritura si nada es aplicable.
116
+ - `ast`: Edita comprendiendo la estructura sintáctica del archivo (Python con
117
+ `ast`, otros lenguajes con `tree-sitter` si está instalado). Soporta renombrar
118
+ símbolos, insertar imports y recibir el código completo resultante.
113
119
  - `parche`: Fuerza la aplicación de parches unificados para ediciones precisas.
114
120
  - `sobrescribir`: Sobrescribe el archivo completo.
115
121
  - **`--editor aider`** (por defecto): Mantiene el flujo de edición con Aider para máxima compatibilidad.
@@ -120,6 +126,9 @@ snapcontext "añadir endpoint de métricas" --editor propio
120
126
 
121
127
  # Forzar modo parche
122
128
  snapcontext "corregir tipado en login" --editor propio --modo-edicion parche
129
+
130
+ # Refactorización estructural con AST
131
+ snapcontext "renombrar la variable 'usuario' a 'cuenta'" --editor propio --modo-edicion ast
123
132
  ```
124
133
 
125
134
  ## 🧭 Modos y alias
@@ -266,7 +275,7 @@ snapcontext --plan "tarea" --no-confirmar
266
275
 
267
276
  1. El proveedor de IA descompone la tarea en pasos JSON:
268
277
  ```json
269
- {"descripcion": "...", "accion": "editar|ejecutar|consultar",
278
+ {"descripcion": "...", "accion": "editar|ejecutar|consultar|mcp",
270
279
  "archivos": ["..."], "comando": "...",
271
280
  "dependencias": [1, 2],
272
281
  "condicion": "archivo_existe('src/main.py')"}
@@ -274,7 +283,8 @@ snapcontext --plan "tarea" --no-confirmar
274
283
  2. Los pasos se muestran numerados y se pide confirmación.
275
284
  3. Cada paso se ejecuta secuencialmente: **editar** usa el pipeline completo
276
285
  (`_planificar` + `_bucle_test`), **ejecutar** lanza comandos shell,
277
- **consultar** pregunta al proveedor.
286
+ **consultar** pregunta al proveedor y **mcp** (v2.3.0) ejecuta una
287
+ herramienta MCP guardando su resultado en el contexto del plan.
278
288
  4. Tras cada paso eliges: continuar / reintentar / saltar / abortar. Al final
279
289
  hay un resumen que se guarda en `historial.json`.
280
290
 
@@ -287,10 +297,40 @@ snapcontext --plan "tarea" --no-confirmar
287
297
  - `archivo_existe('src/main.py')`
288
298
  - `archivo_contiene('src/main.py', 'def main')`
289
299
  - `comando_exito('flutter test')`
300
+ - `variable_existe('mi_variable')` *(v2.3.0)*
301
+
302
+ Además, desde v2.3.0 acepta **comparaciones dinámicas** con resultados
303
+ de pasos previos o variables dejadas en el contexto (por pasos `mcp`):
304
+
305
+ ```json
306
+ {"condicion": "pasos[0].resultado == 'ok'"}
307
+ {"condicion": "resultados.mi_variable != ''"}
308
+ ```
290
309
 
291
310
  Si la condición es falsa, el paso se salta (con aviso) y el plan continúa.
311
+ ### Pasos MCP y contexto dinámico (v2.3.0)
312
+
313
+ Un paso con `"accion": "mcp"` ejecuta una herramienta MCP del registro
314
+ (`grep`, `read_file`, `list_files`, `ast`, `git_status`, `git_diff`,
315
+ `execute_command`, ...) usando los campos `"herramienta"` y `"args"`.
316
+ El resultado queda disponible para los pasos posteriores:
317
+
318
+ ```json
319
+ {"descripcion": "buscar referencias", "accion": "mcp",
320
+ "herramienta": "grep", "args": {"patron": "def main"},
321
+ "variable": "coincidencias"}
322
+ {"descripcion": "usar resultado", "accion": "ejecutar",
323
+ "comando": "echo {{coincidencias}}"}
324
+ ```
325
+
326
+ - El resultado siempre se guarda también como `{{resultado}}`; si el paso
327
+ define `"variable"`, se guarda bajo ese nombre.
328
+ - `execute_command` soporta `background=true` (devuelve un `pid`
329
+ consultable con `execute_command_status`) y `capture_output=false` para
330
+ mostrar la salida en tiempo real.
331
+
292
332
  - **Paralelismo**: con `--paralelo N` (junto a `--auto`) se ejecutan hasta N
293
- pasos independientes a la vez; los logs llevan el identificador `[paso N]`:
333
+ pasos independientes a la vez; los logs llevan el identificador `[paso N]`. Desde v2.3.0 el planificador también respeta las dependencias dinámicas: un paso cuya condición usa una variable que otro paso aún no ha producido espera a que esté disponible:
294
334
 
295
335
  ```bash
296
336
  snapcontext --plan "tarea grande" --auto --paralelo 3 --no-confirmar
@@ -59,12 +59,18 @@ snapcontext interactive # o snapcontext --web
59
59
  Auto-detección de proyecto (Flutter, Node/React, Python, Go, Rust…) que ajusta
60
60
  carpetas, extensiones y comandos de test por defecto.
61
61
 
62
- ## 🛠️ Editor Integrado Propio (v2.1.0)
62
+ ## 🛠️ Editor Integrado Propio (v2.2.0)
63
63
 
64
- SnapContext incluye su propio editor integrado con soporte de parches unificados:
64
+ SnapContext incluye su propio editor integrado con soporte de parches unificados
65
+ y edición basada en AST (Árbol Sintáctico) para refactorizaciones precisas:
65
66
  - **`--editor propio`**: Aplica cambios directamente y crea copias de seguridad automáticas en `~/.snapcontext/backups/`.
66
- - **`--modo-edicion {auto,parche,sobrescribir}`**:
67
- - `auto` (por defecto): Genera un parche unificado (`git apply` / `patch`) y hace fallback a sobrescritura si no es aplicable.
67
+ - **`--modo-edicion {auto,parche,sobrescribir,ast}`**:
68
+ - `auto` (por defecto): Genera un parche unificado (`git apply` / `patch`), pero
69
+ para refactorizaciones estructurales intenta primero la edición **AST** y cae a
70
+ sobrescritura si nada es aplicable.
71
+ - `ast`: Edita comprendiendo la estructura sintáctica del archivo (Python con
72
+ `ast`, otros lenguajes con `tree-sitter` si está instalado). Soporta renombrar
73
+ símbolos, insertar imports y recibir el código completo resultante.
68
74
  - `parche`: Fuerza la aplicación de parches unificados para ediciones precisas.
69
75
  - `sobrescribir`: Sobrescribe el archivo completo.
70
76
  - **`--editor aider`** (por defecto): Mantiene el flujo de edición con Aider para máxima compatibilidad.
@@ -75,6 +81,9 @@ snapcontext "añadir endpoint de métricas" --editor propio
75
81
 
76
82
  # Forzar modo parche
77
83
  snapcontext "corregir tipado en login" --editor propio --modo-edicion parche
84
+
85
+ # Refactorización estructural con AST
86
+ snapcontext "renombrar la variable 'usuario' a 'cuenta'" --editor propio --modo-edicion ast
78
87
  ```
79
88
 
80
89
  ## 🧭 Modos y alias
@@ -221,7 +230,7 @@ snapcontext --plan "tarea" --no-confirmar
221
230
 
222
231
  1. El proveedor de IA descompone la tarea en pasos JSON:
223
232
  ```json
224
- {"descripcion": "...", "accion": "editar|ejecutar|consultar",
233
+ {"descripcion": "...", "accion": "editar|ejecutar|consultar|mcp",
225
234
  "archivos": ["..."], "comando": "...",
226
235
  "dependencias": [1, 2],
227
236
  "condicion": "archivo_existe('src/main.py')"}
@@ -229,7 +238,8 @@ snapcontext --plan "tarea" --no-confirmar
229
238
  2. Los pasos se muestran numerados y se pide confirmación.
230
239
  3. Cada paso se ejecuta secuencialmente: **editar** usa el pipeline completo
231
240
  (`_planificar` + `_bucle_test`), **ejecutar** lanza comandos shell,
232
- **consultar** pregunta al proveedor.
241
+ **consultar** pregunta al proveedor y **mcp** (v2.3.0) ejecuta una
242
+ herramienta MCP guardando su resultado en el contexto del plan.
233
243
  4. Tras cada paso eliges: continuar / reintentar / saltar / abortar. Al final
234
244
  hay un resumen que se guarda en `historial.json`.
235
245
 
@@ -242,10 +252,40 @@ snapcontext --plan "tarea" --no-confirmar
242
252
  - `archivo_existe('src/main.py')`
243
253
  - `archivo_contiene('src/main.py', 'def main')`
244
254
  - `comando_exito('flutter test')`
255
+ - `variable_existe('mi_variable')` *(v2.3.0)*
256
+
257
+ Además, desde v2.3.0 acepta **comparaciones dinámicas** con resultados
258
+ de pasos previos o variables dejadas en el contexto (por pasos `mcp`):
259
+
260
+ ```json
261
+ {"condicion": "pasos[0].resultado == 'ok'"}
262
+ {"condicion": "resultados.mi_variable != ''"}
263
+ ```
245
264
 
246
265
  Si la condición es falsa, el paso se salta (con aviso) y el plan continúa.
266
+ ### Pasos MCP y contexto dinámico (v2.3.0)
267
+
268
+ Un paso con `"accion": "mcp"` ejecuta una herramienta MCP del registro
269
+ (`grep`, `read_file`, `list_files`, `ast`, `git_status`, `git_diff`,
270
+ `execute_command`, ...) usando los campos `"herramienta"` y `"args"`.
271
+ El resultado queda disponible para los pasos posteriores:
272
+
273
+ ```json
274
+ {"descripcion": "buscar referencias", "accion": "mcp",
275
+ "herramienta": "grep", "args": {"patron": "def main"},
276
+ "variable": "coincidencias"}
277
+ {"descripcion": "usar resultado", "accion": "ejecutar",
278
+ "comando": "echo {{coincidencias}}"}
279
+ ```
280
+
281
+ - El resultado siempre se guarda también como `{{resultado}}`; si el paso
282
+ define `"variable"`, se guarda bajo ese nombre.
283
+ - `execute_command` soporta `background=true` (devuelve un `pid`
284
+ consultable con `execute_command_status`) y `capture_output=false` para
285
+ mostrar la salida en tiempo real.
286
+
247
287
  - **Paralelismo**: con `--paralelo N` (junto a `--auto`) se ejecutan hasta N
248
- pasos independientes a la vez; los logs llevan el identificador `[paso N]`:
288
+ pasos independientes a la vez; los logs llevan el identificador `[paso N]`. Desde v2.3.0 el planificador también respeta las dependencias dinámicas: un paso cuya condición usa una variable que otro paso aún no ha producido espera a que esté disponible:
249
289
 
250
290
  ```bash
251
291
  snapcontext --plan "tarea grande" --auto --paralelo 3 --no-confirmar
@@ -21,6 +21,22 @@ logs de depuración visibles con ``--depurar`` (marcas ``[Agente…]``).
21
21
  import subprocess
22
22
  from typing import List, Optional, Union
23
23
 
24
+ def _tarea_estructura(tarea: str) -> bool:
25
+ """Heurística simple para `auto`: ¿la tarea parece una refactorización estructural?
26
+
27
+ Si es así, merece la pena intentar primero la edición basada en AST.
28
+ Se normalizan acentos para tolerar variaciones ("renombra"/"renombrar", etc.).
29
+ """
30
+ if not tarea:
31
+ return False
32
+ import unicodedata as _u
33
+
34
+ t = _u.normalize("NFD", tarea.lower())
35
+ t = "".join(c for c in t if not _u.combining(c))
36
+ claves = ("refactor", "renombra", "renombrar", "extra", "extraer",
37
+ "mueve", "mover", "inserta", "insertar", "nueva funcion",
38
+ "crear funcion", "funcion", "extraccion")
39
+ return any(k in t for k in claves)
24
40
 
25
41
  class AgenteContexto:
26
42
  """Agente de Contexto: selecciona los archivos relevantes para la consulta.
@@ -175,6 +191,91 @@ class AgenteEditorPropio:
175
191
  sc.depurar(f"[AgenteEditorPropio] Aplicando parche en '{directorio}'")
176
192
  return sc._aplicar_parche(parche, directorio=directorio)
177
193
 
194
+ def _cadena_modos(self, archivo: str, mensaje: str,
195
+ modo_edicion: str) -> List[str]:
196
+ """Devuelve la cadena de estrategias de edición a intentar para un archivo.
197
+
198
+ - 'sobrescribir' → solo sobrescritura.
199
+ - 'parche' → solo parche unificado (sin fallback).
200
+ - 'ast' → AST y, si falla, sobrescritura.
201
+ - 'auto' → AST (si el lenguaje lo permite y la tarea es estructural),
202
+ luego parche y, por último, sobrescritura.
203
+ """
204
+ import snapcontext as sc
205
+
206
+ if modo_edicion == "sobrescribir":
207
+ return ["sobrescribir"]
208
+ if modo_edicion == "parche":
209
+ return ["parche"]
210
+ if modo_edicion == "ast":
211
+ return ["ast", "sobrescribir"]
212
+ # auto: heurística simple según lenguaje y tarea
213
+ if sc._ast_disponible(archivo) and _tarea_estructura(mensaje):
214
+ return ["ast", "parche", "sobrescribir"]
215
+ return ["parche", "sobrescribir"]
216
+
217
+ def editar_ast(self, archivo: str, tarea: str,
218
+ directorio: str = ".", modelo: Optional[str] = None) -> bool:
219
+ """Edita ``archivo`` con base en su AST usando el proveedor de IA."""
220
+ import snapcontext as sc
221
+
222
+ pref = sc.cargar_configuracion()
223
+ proveedor = pref.get("provider") or sc.PROVEEDOR_DEFECTO
224
+ sc.depurar(f"[AgenteEditorPropio] Editando por AST '{archivo}' en '{directorio}'")
225
+ return sc._editor_ast(archivo, tarea, directorio=directorio,
226
+ proveedor=proveedor, modelo=modelo)
227
+
228
+ def _aplicar_modo_parche(self, archivo: str, mensaje: str,
229
+ contenido_actual: str, modelo: Optional[str],
230
+ directorio: str) -> bool:
231
+ """Intenta editar `archivo` pidiendo un parche unificado al proveedor."""
232
+ import snapcontext as sc
233
+
234
+ pref = sc.cargar_configuracion()
235
+ 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)
254
+ return False
255
+
256
+ def _aplicar_modo_sobrescribir(self, archivo: str, mensaje: str,
257
+ contenido_actual: str, modelo: Optional[str],
258
+ directorio: str) -> bool:
259
+ """Sobrescribe `archivo` con el código completo que devuelve el proveedor."""
260
+ import snapcontext as sc
261
+
262
+ pref = sc.cargar_configuracion()
263
+ 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])
277
+ return self.sobrescribir(archivo, nuevo_contenido, directorio)
278
+
178
279
  def ejecutar(
179
280
  self,
180
281
  archivos: List[str],
@@ -187,14 +288,13 @@ class AgenteEditorPropio:
187
288
 
188
289
  - modo_edicion == 'parche': intenta aplicar parche unificado (falla si no puede).
189
290
  - modo_edicion == 'sobrescribir': aplica directamente sobrescritura de archivo.
190
- - modo_edicion == 'auto': intenta primero parche unificado; si falla o no es diff,
191
- hace fallback a sobrescritura.
291
+ - modo_edicion == 'ast': edita con base en el AST y cae a sobrescritura si falla.
292
+ - modo_edicion == 'auto': decide por heurística (AST para refactorizaciones,
293
+ parche, y fallback a sobrescritura).
192
294
  """
193
295
  import snapcontext as sc
194
296
  from pathlib import Path
195
297
 
196
- pref = sc.cargar_configuracion()
197
- proveedor = pref.get("provider") or sc.PROVEEDOR_DEFECTO
198
298
  raiz = Path(directorio).resolve()
199
299
  todo_ok = True
200
300
 
@@ -203,75 +303,77 @@ class AgenteEditorPropio:
203
303
  contenido_actual = ""
204
304
  if camino.is_file():
205
305
  try:
206
- contenido_actual = camino.read_text(encoding="utf-8", errors="replace")
306
+ contenido_actual = camino.read_text(encoding="utf-8",
307
+ errors="replace")
207
308
  except Exception:
208
309
  pass
209
310
 
210
- if modo_edicion in ("parche", "auto"):
211
- prompt = (
212
- f"Genera un parche unificado (unified diff) que modifique el archivo para cumplir con la tarea. "
213
- f"El parche debe ser aplicable con 'patch -p1' o 'git apply' (encabezados --- a/{arch} y +++ b/{arch}).\n\n"
214
- f"Tarea: {mensaje}\n"
215
- f"Archivo: {arch}\n\n"
216
- f"Contenido actual:\n```\n{contenido_actual}\n```\n\n"
217
- f"Devuelve ÚNICAMENTE el diff unificado, sin explicaciones ni rodeos."
218
- )
311
+ cadena = self._cadena_modos(arch, mensaje, modo_edicion)
312
+ conseguido = False
313
+ for estrategia in cadena:
219
314
  try:
220
- respuesta = sc._enviar_al_proveedor(
221
- proveedor, modelo,
222
- [{"role": "user", "content": prompt}]
223
- )
224
- # Limpiar delimitadores markdown si vinieron
225
- diff_limpio = respuesta
226
- if "--- " in diff_limpio and "+++ " in diff_limpio:
227
- # Extraer solo desde ---
228
- idx_inicio = diff_limpio.find("--- ")
229
- diff_limpio = diff_limpio[idx_inicio:]
230
- if "```" in diff_limpio:
231
- diff_limpio = diff_limpio[:diff_limpio.find("```")]
232
-
233
- # Si parece un diff unificado válido, intentar aplicarlo
234
- if ("--- " in diff_limpio and "+++ " in diff_limpio and "@@" in diff_limpio):
235
- if self.aplicar_parche(diff_limpio, str(raiz)):
236
- continue
237
- sc.aviso(f"[AgenteEditorPropio] Parche para '{arch}' falló al aplicarse.")
238
- if modo_edicion == "parche":
239
- todo_ok = False
240
- continue
315
+ if estrategia == "ast":
316
+ conseguido = self.editar_ast(arch, mensaje,
317
+ str(raiz), modelo)
318
+ elif estrategia == "parche":
319
+ conseguido = self._aplicar_modo_parche(
320
+ arch, mensaje, contenido_actual, modelo, str(raiz))
321
+ elif estrategia == "sobrescribir":
322
+ conseguido = self._aplicar_modo_sobrescribir(
323
+ arch, mensaje, contenido_actual, modelo, str(raiz))
324
+ else:
325
+ conseguido = False
241
326
  except Exception as exc:
242
- sc.depurar(f"[AgenteEditorPropio] Error pidiendo parche para '{arch}': {exc}")
243
- if modo_edicion == "parche":
244
- sc.error(f"Error generando parche para {arch}: {exc}")
245
- todo_ok = False
246
- continue
247
-
248
- # Fallback o modo sobrescribir
249
- sc.depurar(f"[AgenteEditorPropio] Aplicando edición por sobrescritura para '{arch}'...")
250
- prompt_sobrescribir = (
251
- f"Modifica el siguiente archivo para cumplir con la tarea.\n\n"
252
- f"Tarea: {mensaje}\n"
253
- f"Archivo: {arch}\n\n"
254
- f"Contenido actual:\n```\n{contenido_actual}\n```\n\n"
255
- f"Devuelve ÚNICAMENTE el código completo resultante, sin explicaciones ni markdown."
256
- )
257
- try:
258
- nuevo_contenido = sc._enviar_al_proveedor(
259
- proveedor, modelo,
260
- [{"role": "user", "content": prompt_sobrescribir}]
261
- )
262
- if nuevo_contenido.startswith("```"):
263
- lineas = nuevo_contenido.splitlines()
264
- if len(lineas) >= 2 and lineas[-1].startswith("```"):
265
- nuevo_contenido = "\n".join(lineas[1:-1])
266
- if not self.sobrescribir(arch, nuevo_contenido, str(raiz)):
267
- todo_ok = False
268
- except Exception as exc:
269
- sc.error(f"Error generando cambios por sobrescritura para {arch}: {exc}")
327
+ sc.depurar(
328
+ f"[AgenteEditorPropio] {estrategia} falló para '{arch}': {exc}")
329
+ conseguido = False
330
+ if conseguido:
331
+ break
332
+ if not conseguido:
333
+ sc.error(f"[AgenteEditorPropio] No se pudo editar '{arch}'.")
270
334
  todo_ok = False
271
335
 
272
336
  return todo_ok
273
337
 
274
338
 
339
+ class AgenteEditorAST:
340
+ """Agente Editor AST (Fase 3): ediciones precisas basadas en árbol sintáctico.
341
+
342
+ Trabaja directamente con el AST del archivo (Python con ``ast``; otros
343
+ lenguajes con ``tree-sitter`` si está instalado) pidiendo al proveedor
344
+ operaciones de modificación del árbol o el código completo resultante.
345
+ """
346
+
347
+ def editar(
348
+ self,
349
+ archivos: List[str],
350
+ mensaje: str,
351
+ directorio: str = ".",
352
+ modelo: Optional[str] = None,
353
+ ) -> bool:
354
+ """Aplica a cada archivo una edición guiada por AST. Devuelve bool."""
355
+ todo = True
356
+ for archivo in archivos:
357
+ if not self.editar_archivo(archivo, mensaje,
358
+ directorio=directorio, modelo=modelo):
359
+ todo = False
360
+ return todo
361
+
362
+ def editar_archivo(
363
+ self,
364
+ archivo: str,
365
+ tarea: str,
366
+ directorio: str = ".",
367
+ modelo: Optional[str] = None,
368
+ ) -> bool:
369
+ """Edita un único archivo con AST. Devuelve ``True`` si tuvo éxito."""
370
+ import snapcontext as sc
371
+
372
+ pref = sc.cargar_configuracion()
373
+ proveedor = pref.get("provider") or sc.PROVEEDOR_DEFECTO
374
+ sc.depurar(f"[AgenteEditorAST] AST sobre '{archivo}' en '{directorio}'")
375
+ return sc._editor_ast(archivo, tarea, directorio=directorio,
376
+ proveedor=proveedor, modelo=modelo)
275
377
 
276
378
 
277
379
  class AgenteTester:
@@ -20,7 +20,8 @@ import shlex
20
20
  import sys
21
21
  from typing import List, Optional, Tuple
22
22
 
23
- from agentes import AgenteContexto, AgenteEditor, AgenteEditorPropio, AgenteTester
23
+ from agentes import (AgenteContexto, AgenteEditor, AgenteEditorAST,
24
+ AgenteEditorPropio, AgenteTester)
24
25
 
25
26
  # Centinela diferenciado del None de "aborto": _planificar lo devuelve cuando
26
27
  # el pipeline termina de forma exitosa y temprana (p. ej. --vista-previa).
@@ -40,6 +41,7 @@ class Orquestador:
40
41
  self.agente_contexto = AgenteContexto()
41
42
  self.agente_editor = AgenteEditor()
42
43
  self.agente_editor_propio = AgenteEditorPropio()
44
+ self.agente_editor_ast = AgenteEditorAST()
43
45
  self.agente_tester = AgenteTester()
44
46
  self.evento_callback = evento_callback
45
47
 
@@ -1,90 +1,90 @@
1
- [build-system]
2
- requires = ["setuptools>=77"]
3
- build-backend = "setuptools.build_meta"
4
-
5
- [project]
6
- # Nombre verificado disponible en PyPI (18/08/2026): pypi.org/pypi/snapcontext → 404.
7
- # Alternativas si algún día estuviera ocupado: "snapcontext-cli" o "snapcontext-tool".
8
- name = "snapcontext"
9
- version = "2.1.0"
10
- description = "SnapContext: Asistente de IA con contexto automático para desarrollo"
11
- readme = "README.md"
12
- requires-python = ">=3.9"
13
- # PEP 639: licencia SPDX + fichero de licencia incluido en el paquete.
14
- license = "MIT"
15
- license-files = ["LICENSE"]
16
- authors = [
17
- # TODO: cambia por tu nombre y correo reales antes de publicar.
18
- { name = "SnapContext Contributors", email = "tu@email.com" },
19
- ]
20
- keywords = [
21
- "ai", "aider", "gemini", "ollama", "deepseek", "groq", "claude", "anthropic",
22
- "flutter", "supabase", "cli",
23
- ]
24
- classifiers = [
25
- "Development Status :: 5 - Production/Stable",
26
- "Environment :: Console",
27
- "Intended Audience :: Developers",
28
- "Operating System :: OS Independent",
29
- "Programming Language :: Python :: 3",
30
- "Programming Language :: Python :: 3.9",
31
- "Programming Language :: Python :: 3.10",
32
- "Programming Language :: Python :: 3.11",
33
- "Programming Language :: Python :: 3.12",
34
- "Programming Language :: Python :: 3.13",
35
- "Topic :: Software Development :: Code Generators",
36
- ]
37
- dependencies = [
38
- "google-generativeai>=0.8.3",
39
- "openai>=1.30.0",
40
- ]
41
-
42
- [project.optional-dependencies]
43
- dev = [
44
- "aider-chat>=0.67.0",
45
- "build>=1.2.0",
46
- "twine>=5.0.0",
47
- ]
48
- # Menú interactivo de selección de proveedor (--provider y --local omitidos).
49
- interactive = [
50
- "questionary>=1.10.0",
51
- ]
52
- # Claude (Anthropic) como proveedor de IA (--provider anthropic, --chat).
53
- anthropic = [
54
- "anthropic>=0.30.0",
55
- ]
56
- # Interfaz web (FastAPI + WebSockets) con --web.
57
- web = [
58
- "fastapi>=0.110.0",
59
- "uvicorn[standard]>=0.29.0",
60
- ]
61
- # Búsqueda semántica local con embeddings (--search, pre-filtro del planificador).
62
- embeddings = [
63
- "sentence-transformers>=3.0.0",
64
- "torch>=2.0.0",
65
- ]
66
- # MCP avanzado (v1.4.0): análisis sintáctico multi-lenguaje con tree-sitter
67
- # (herramienta `ast_avanzado`). Sin él, cae a `ast` de la stdlib (solo Python).
68
- mcp_avanzado = [
69
- "tree-sitter>=0.20.0",
70
- "tree-sitter-languages>=1.10.0",
71
- ]
72
-
73
- [project.urls]
74
- # TODO: apunta a tu repositorio real (no bloquea la publicación si lo dejas).
75
- Homepage = "https://github.com/NicolasBruna24/snapcontext"
76
- Repository = "https://github.com/NicolasBruna24/snapcontext"
77
- "Bug Tracker" = "https://github.com/NicolasBruna24/snapcontext/issues"
78
-
79
- # Entry point: expone el comando `snapcontext` que llama a main().
80
- [project.scripts]
81
- snapcontext = "snapcontext:main"
82
-
83
- [tool.setuptools]
84
- # Módulos en la raíz (sin paquete): CLI + arquitectura de agentes.
85
- py-modules = ["snapcontext", "agentes", "orquestador"]
86
- packages = ["web"]
87
-
88
- [tool.setuptools.package-data]
89
- # El estático de la interfaz web debe viajar dentro del paquete instalado.
1
+ [build-system]
2
+ requires = ["setuptools>=77"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ # Nombre verificado disponible en PyPI (18/08/2026): pypi.org/pypi/snapcontext → 404.
7
+ # Alternativas si algún día estuviera ocupado: "snapcontext-cli" o "snapcontext-tool".
8
+ name = "snapcontext"
9
+ version = "2.3.0"
10
+ description = "SnapContext: Asistente de IA con contexto automático para desarrollo"
11
+ readme = "README.md"
12
+ requires-python = ">=3.9"
13
+ # PEP 639: licencia SPDX + fichero de licencia incluido en el paquete.
14
+ license = "MIT"
15
+ license-files = ["LICENSE"]
16
+ authors = [
17
+ # TODO: cambia por tu nombre y correo reales antes de publicar.
18
+ { name = "SnapContext Contributors", email = "tu@email.com" },
19
+ ]
20
+ keywords = [
21
+ "ai", "aider", "gemini", "ollama", "deepseek", "groq", "claude", "anthropic",
22
+ "flutter", "supabase", "cli",
23
+ ]
24
+ classifiers = [
25
+ "Development Status :: 5 - Production/Stable",
26
+ "Environment :: Console",
27
+ "Intended Audience :: Developers",
28
+ "Operating System :: OS Independent",
29
+ "Programming Language :: Python :: 3",
30
+ "Programming Language :: Python :: 3.9",
31
+ "Programming Language :: Python :: 3.10",
32
+ "Programming Language :: Python :: 3.11",
33
+ "Programming Language :: Python :: 3.12",
34
+ "Programming Language :: Python :: 3.13",
35
+ "Topic :: Software Development :: Code Generators",
36
+ ]
37
+ dependencies = [
38
+ "google-generativeai>=0.8.3",
39
+ "openai>=1.30.0",
40
+ ]
41
+
42
+ [project.optional-dependencies]
43
+ dev = [
44
+ "aider-chat>=0.67.0",
45
+ "build>=1.2.0",
46
+ "twine>=5.0.0",
47
+ ]
48
+ # Menú interactivo de selección de proveedor (--provider y --local omitidos).
49
+ interactive = [
50
+ "questionary>=1.10.0",
51
+ ]
52
+ # Claude (Anthropic) como proveedor de IA (--provider anthropic, --chat).
53
+ anthropic = [
54
+ "anthropic>=0.30.0",
55
+ ]
56
+ # Interfaz web (FastAPI + WebSockets) con --web.
57
+ web = [
58
+ "fastapi>=0.110.0",
59
+ "uvicorn[standard]>=0.29.0",
60
+ ]
61
+ # Búsqueda semántica local con embeddings (--search, pre-filtro del planificador).
62
+ embeddings = [
63
+ "sentence-transformers>=3.0.0",
64
+ "torch>=2.0.0",
65
+ ]
66
+ # MCP avanzado (v1.4.0): análisis sintáctico multi-lenguaje con tree-sitter
67
+ # (herramienta `ast_avanzado`). Sin él, cae a `ast` de la stdlib (solo Python).
68
+ mcp_avanzado = [
69
+ "tree-sitter>=0.20.0",
70
+ "tree-sitter-languages>=1.10.0",
71
+ ]
72
+
73
+ [project.urls]
74
+ # TODO: apunta a tu repositorio real (no bloquea la publicación si lo dejas).
75
+ Homepage = "https://github.com/NicolasBruna24/snapcontext"
76
+ Repository = "https://github.com/NicolasBruna24/snapcontext"
77
+ "Bug Tracker" = "https://github.com/NicolasBruna24/snapcontext/issues"
78
+
79
+ # Entry point: expone el comando `snapcontext` que llama a main().
80
+ [project.scripts]
81
+ snapcontext = "snapcontext:main"
82
+
83
+ [tool.setuptools]
84
+ # Módulos en la raíz (sin paquete): CLI + arquitectura de agentes.
85
+ py-modules = ["snapcontext", "agentes", "orquestador"]
86
+ packages = ["web"]
87
+
88
+ [tool.setuptools.package-data]
89
+ # El estático de la interfaz web debe viajar dentro del paquete instalado.
90
90
  "web" = ["static/index.html"]