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.
- {snapcontext-2.1.0/snapcontext.egg-info → snapcontext-2.3.0}/PKG-INFO +48 -8
- {snapcontext-2.1.0 → snapcontext-2.3.0}/README.md +47 -7
- {snapcontext-2.1.0 → snapcontext-2.3.0}/agentes.py +165 -63
- {snapcontext-2.1.0 → snapcontext-2.3.0}/orquestador.py +3 -1
- {snapcontext-2.1.0 → snapcontext-2.3.0}/pyproject.toml +89 -89
- {snapcontext-2.1.0 → snapcontext-2.3.0/snapcontext.egg-info}/PKG-INFO +48 -8
- {snapcontext-2.1.0 → snapcontext-2.3.0}/snapcontext.egg-info/SOURCES.txt +2 -0
- {snapcontext-2.1.0 → snapcontext-2.3.0}/snapcontext.py +737 -31
- {snapcontext-2.1.0 → snapcontext-2.3.0}/tests/test_auto_017.py +190 -190
- {snapcontext-2.1.0 → snapcontext-2.3.0}/tests/test_diff_editor_210.py +103 -103
- snapcontext-2.3.0/tests/test_editor_ast_220.py +181 -0
- {snapcontext-2.1.0 → snapcontext-2.3.0}/tests/test_editor_propio_200.py +91 -91
- {snapcontext-2.1.0 → snapcontext-2.3.0}/tests/test_exe_150.py +72 -72
- {snapcontext-2.1.0 → snapcontext-2.3.0}/tests/test_jetbrains_170.py +115 -115
- {snapcontext-2.1.0 → snapcontext-2.3.0}/tests/test_mcp_014.py +225 -225
- {snapcontext-2.1.0 → snapcontext-2.3.0}/tests/test_mcp_140.py +255 -255
- {snapcontext-2.1.0 → snapcontext-2.3.0}/tests/test_memoria_015.py +186 -186
- {snapcontext-2.1.0 → snapcontext-2.3.0}/tests/test_permisos_013.py +190 -190
- {snapcontext-2.1.0 → snapcontext-2.3.0}/tests/test_plan_012.py +279 -279
- snapcontext-2.3.0/tests/test_plan_mcp_230.py +223 -0
- {snapcontext-2.1.0 → snapcontext-2.3.0}/tests/test_v010.py +248 -248
- {snapcontext-2.1.0 → snapcontext-2.3.0}/tests/test_validacion_130.py +133 -133
- {snapcontext-2.1.0 → snapcontext-2.3.0}/tests/test_vscode_016.py +94 -94
- {snapcontext-2.1.0 → snapcontext-2.3.0}/tests/test_web_160.py +95 -95
- {snapcontext-2.1.0 → snapcontext-2.3.0}/web/app.py +392 -392
- {snapcontext-2.1.0 → snapcontext-2.3.0}/LICENSE +0 -0
- {snapcontext-2.1.0 → snapcontext-2.3.0}/setup.cfg +0 -0
- {snapcontext-2.1.0 → snapcontext-2.3.0}/snapcontext.egg-info/__pycache__/top_level.cpython-313.pyc +0 -0
- {snapcontext-2.1.0 → snapcontext-2.3.0}/snapcontext.egg-info/dependency_links.txt +0 -0
- {snapcontext-2.1.0 → snapcontext-2.3.0}/snapcontext.egg-info/entry_points.txt +0 -0
- {snapcontext-2.1.0 → snapcontext-2.3.0}/snapcontext.egg-info/requires.txt +0 -0
- {snapcontext-2.1.0 → snapcontext-2.3.0}/snapcontext.egg-info/top_level.txt +0 -0
- {snapcontext-2.1.0 → snapcontext-2.3.0}/tests/test_agentes.py +0 -0
- {snapcontext-2.1.0 → snapcontext-2.3.0}/tests/test_deteccion.py +0 -0
- {snapcontext-2.1.0 → snapcontext-2.3.0}/tests/test_embeddings_110.py +0 -0
- {snapcontext-2.1.0 → snapcontext-2.3.0}/tests/test_orquestador.py +0 -0
- {snapcontext-2.1.0 → snapcontext-2.3.0}/tests/test_web_120.py +0 -0
- {snapcontext-2.1.0 → snapcontext-2.3.0}/web/__init__.py +0 -0
- {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.
|
|
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.
|
|
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`)
|
|
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.
|
|
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`)
|
|
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 == '
|
|
191
|
-
|
|
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",
|
|
306
|
+
contenido_actual = camino.read_text(encoding="utf-8",
|
|
307
|
+
errors="replace")
|
|
207
308
|
except Exception:
|
|
208
309
|
pass
|
|
209
310
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
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
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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(
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
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,
|
|
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.
|
|
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"]
|