snapcontext 3.3.0__tar.gz → 3.5.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {snapcontext-3.3.0/snapcontext.egg-info → snapcontext-3.5.0}/PKG-INFO +85 -3
- {snapcontext-3.3.0 → snapcontext-3.5.0}/README.md +84 -2
- {snapcontext-3.3.0 → snapcontext-3.5.0}/agentes.py +210 -45
- {snapcontext-3.3.0 → snapcontext-3.5.0}/orquestador.py +6 -1
- {snapcontext-3.3.0 → snapcontext-3.5.0}/pyproject.toml +1 -1
- {snapcontext-3.3.0 → snapcontext-3.5.0/snapcontext.egg-info}/PKG-INFO +85 -3
- {snapcontext-3.3.0 → snapcontext-3.5.0}/snapcontext.egg-info/SOURCES.txt +2 -0
- {snapcontext-3.3.0 → snapcontext-3.5.0}/snapcontext.py +573 -5
- snapcontext-3.5.0/tests/test_asesor_350.py +265 -0
- {snapcontext-3.3.0 → snapcontext-3.5.0}/tests/test_auto_017.py +1 -1
- {snapcontext-3.3.0 → snapcontext-3.5.0}/tests/test_diagnostico_310.py +1 -1
- {snapcontext-3.3.0 → snapcontext-3.5.0}/tests/test_diff_editor_210.py +1 -1
- {snapcontext-3.3.0 → snapcontext-3.5.0}/tests/test_editor_330.py +1 -1
- {snapcontext-3.3.0 → snapcontext-3.5.0}/tests/test_editor_ast_220.py +1 -1
- {snapcontext-3.3.0 → snapcontext-3.5.0}/tests/test_editor_propio_200.py +1 -1
- {snapcontext-3.3.0 → snapcontext-3.5.0}/tests/test_exe_150.py +1 -1
- {snapcontext-3.3.0 → snapcontext-3.5.0}/tests/test_jetbrains_170.py +1 -1
- {snapcontext-3.3.0 → snapcontext-3.5.0}/tests/test_mcp_014.py +1 -1
- {snapcontext-3.3.0 → snapcontext-3.5.0}/tests/test_mcp_140.py +1 -1
- {snapcontext-3.3.0 → snapcontext-3.5.0}/tests/test_memoria_015.py +1 -1
- {snapcontext-3.3.0 → snapcontext-3.5.0}/tests/test_memoria_300.py +1 -1
- {snapcontext-3.3.0 → snapcontext-3.5.0}/tests/test_permisos_013.py +1 -1
- {snapcontext-3.3.0 → snapcontext-3.5.0}/tests/test_plan_012.py +1 -1
- {snapcontext-3.3.0 → snapcontext-3.5.0}/tests/test_plan_mcp_230.py +1 -1
- {snapcontext-3.3.0 → snapcontext-3.5.0}/tests/test_v010.py +1 -1
- {snapcontext-3.3.0 → snapcontext-3.5.0}/tests/test_validacion_130.py +1 -1
- snapcontext-3.5.0/tests/test_validacion_sintaxis_340.py +195 -0
- {snapcontext-3.3.0 → snapcontext-3.5.0}/tests/test_vscode_016.py +1 -1
- {snapcontext-3.3.0 → snapcontext-3.5.0}/tests/test_web_160.py +1 -1
- {snapcontext-3.3.0 → snapcontext-3.5.0}/web/app.py +7 -0
- {snapcontext-3.3.0 → snapcontext-3.5.0}/LICENSE +0 -0
- {snapcontext-3.3.0 → snapcontext-3.5.0}/setup.cfg +0 -0
- {snapcontext-3.3.0 → snapcontext-3.5.0}/snapcontext.egg-info/__pycache__/top_level.cpython-313.pyc +0 -0
- {snapcontext-3.3.0 → snapcontext-3.5.0}/snapcontext.egg-info/dependency_links.txt +0 -0
- {snapcontext-3.3.0 → snapcontext-3.5.0}/snapcontext.egg-info/entry_points.txt +0 -0
- {snapcontext-3.3.0 → snapcontext-3.5.0}/snapcontext.egg-info/requires.txt +0 -0
- {snapcontext-3.3.0 → snapcontext-3.5.0}/snapcontext.egg-info/top_level.txt +0 -0
- {snapcontext-3.3.0 → snapcontext-3.5.0}/tests/test_agentes.py +0 -0
- {snapcontext-3.3.0 → snapcontext-3.5.0}/tests/test_bienvenida_311.py +0 -0
- {snapcontext-3.3.0 → snapcontext-3.5.0}/tests/test_deteccion.py +0 -0
- {snapcontext-3.3.0 → snapcontext-3.5.0}/tests/test_embeddings_110.py +0 -0
- {snapcontext-3.3.0 → snapcontext-3.5.0}/tests/test_orquestador.py +0 -0
- {snapcontext-3.3.0 → snapcontext-3.5.0}/tests/test_vscode_ts_320.py +0 -0
- {snapcontext-3.3.0 → snapcontext-3.5.0}/tests/test_web_120.py +0 -0
- {snapcontext-3.3.0 → snapcontext-3.5.0}/web/__init__.py +0 -0
- {snapcontext-3.3.0 → snapcontext-3.5.0}/web/static/index.html +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: snapcontext
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.5.0
|
|
4
4
|
Summary: SnapContext: Asistente de IA con contexto automático para desarrollo
|
|
5
5
|
Author-email: SnapContext Contributors <tu@email.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -45,7 +45,7 @@ Dynamic: license-file
|
|
|
45
45
|
|
|
46
46
|
# SnapContext
|
|
47
47
|
|
|
48
|
-

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

|
|
@@ -199,8 +199,81 @@ snapcontext "corregir tipado en login" --editor propio --modo-edicion parche
|
|
|
199
199
|
|
|
200
200
|
# Refactorización estructural con AST
|
|
201
201
|
snapcontext "renombrar la variable 'usuario' a 'cuenta'" --editor propio --modo-edicion ast
|
|
202
|
+
|
|
203
|
+
# Validación de sintaxis desactivada (comportamiento previo a v3.4.0)
|
|
204
|
+
snapcontext "corregir tipado" --editor propio --no-validar-sintaxis
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Validación de sintaxis antes de guardar (v3.4.0)
|
|
208
|
+
|
|
209
|
+
Cuando uses `--editor propio`, SnapContext **valida la sintaxis** del código que
|
|
210
|
+
ha generado la IA **antes de escribirlo en disco**. Si la validación falla, el
|
|
211
|
+
cambio se rechaza y se le envía el error al proveedor para que lo corrija,
|
|
212
|
+
repitiéndose hasta `--max-intentos-validacion` intentos (por defecto 3). Si tras
|
|
213
|
+
esos intentos el código sigue roto, se **cancela la edición** y el archivo queda
|
|
214
|
+
intacto.
|
|
215
|
+
|
|
216
|
+
- Se detecta el lenguaje del archivo y se usa el validador adecuado:
|
|
217
|
+
Python (`py_compile`), JavaScript/TypeScript (`node --check`), Dart
|
|
218
|
+
(`dart analyze` → `dart format`), Go (`go build -n` → `gofmt -e`), Rust
|
|
219
|
+
(`rustc --parse-only`), Java (`javac -Xlint:none`), C/C++ (`gcc`/`clang
|
|
220
|
+
-fsyntax-only`). Si no hay validador o comando disponible, se omite la
|
|
221
|
+
validación (solo aviso en logs con `--depurar`).
|
|
222
|
+
- La validación se hace sobre un **archivo temporal**, nunca toca el original.
|
|
223
|
+
- Flag `--validar` (por defecto activado) / `--no-validar-sintaxis` para
|
|
224
|
+
desactivarla. Nota: el nombre usa `--no-validar-sintaxis` porque
|
|
225
|
+
`--no-validar` ya es alias de `--iniciar-proyecto`.
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
# Desactivar la validación por completo (comportamiento anterior)
|
|
229
|
+
snapcontext "..." --editor propio --no-validar-sintaxis
|
|
230
|
+
|
|
231
|
+
# Ajustar el número de reintentos
|
|
232
|
+
snapcontext "..." --editor propio --max-intentos-validacion 5
|
|
202
233
|
```
|
|
203
234
|
|
|
235
|
+
### 🧠 Asesor de código proactivo (v3.5.0)
|
|
236
|
+
|
|
237
|
+
SnapContext no solo ejecuta órdenes: también **analiza tu código por su cuenta**
|
|
238
|
+
y te sugiere mejoras (refactorizaciones, deuda técnica, optimizaciones).
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
# Análisis informativo (nunca modifica código)
|
|
242
|
+
snapcontext --asesor
|
|
243
|
+
snapcontext --sugerir # alias
|
|
244
|
+
|
|
245
|
+
# Aplicar automáticamente las mejoras seguras (renombrar símbolos);
|
|
246
|
+
# cada cambio se valida antes de guardarse y se descarta si rompe la sintaxis
|
|
247
|
+
snapcontext --asesor-auto
|
|
248
|
+
|
|
249
|
+
# Ajustar la sensibilidad del detector de funciones largas (defecto: 20)
|
|
250
|
+
snapcontext --asesor --asesor-umbral 30
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
**Qué detecta:**
|
|
254
|
+
|
|
255
|
+
- 🔴 **Prioridad alta**: `except:` desnudos (capturan todo sin querer).
|
|
256
|
+
- 🟡 **Prioridad media**: funciones largas (> 20 líneas), clases con demasiadas
|
|
257
|
+
responsabilidades (> 10 métodos), bloques de código duplicados entre archivos,
|
|
258
|
+
comparaciones `== None` y patrones obsoletos (`.has_key()`, Python 2).
|
|
259
|
+
- 🔵 **Prioridad baja**: nombres poco descriptivos (`d`, `tmp`...) con una
|
|
260
|
+
sugerencia concreta de renombrado.
|
|
261
|
+
|
|
262
|
+
Cada sugerencia incluye descripción, ubicación `archivo:línea`, solución
|
|
263
|
+
propuesta y prioridad. Los umbrales se personalizan en
|
|
264
|
+
`~/.snapcontext/config.json`:
|
|
265
|
+
|
|
266
|
+
```json
|
|
267
|
+
{ "asesor": { "funcion_larga": 30, "clase_metodos": 15, "duplicado_lineas": 8 } }
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
El asesor también está disponible:
|
|
271
|
+
|
|
272
|
+
- **En el chat**: comando `/asesor` (o `/sugerir`) dentro de `--chat`.
|
|
273
|
+
- **En el planificador**: paso `{"accion": "asesor", "descripcion": "..."}` —
|
|
274
|
+
cada sugerencia se presenta para aceptarla o rechazarla individualmente.
|
|
275
|
+
- **En la web**: acción *Asesor* que muestra las sugerencias en un panel.
|
|
276
|
+
|
|
204
277
|
## 🧭 Modos y alias
|
|
205
278
|
|
|
206
279
|
| Modo | Comando |
|
|
@@ -475,7 +548,7 @@ snapcontext --plan "..." # commits '
|
|
|
475
548
|
(requiere `ANTHROPIC_API_KEY` y `pip install snapcontext[anthropic]`). Modelo por
|
|
476
549
|
defecto: `claude-3-5-sonnet-20241022`.
|
|
477
550
|
- **Modo chat interactivo**: `snapcontext --chat` abre un REPL con los comandos
|
|
478
|
-
`/salir`, `/archivos`, `/limpiar`, `/seleccion <consulta>`,
|
|
551
|
+
`/salir`, `/archivos`, `/limpiar`, `/seleccion <consulta>`, `/asesor`,
|
|
479
552
|
`/provider <proveedor>`, `/historial` y `/ayuda`. Cualquier otro texto se envía
|
|
480
553
|
al proveedor actual manteniendo la conversación.
|
|
481
554
|
- **Memoria persistente**: cada tarea se guarda en `~/.snapcontext/historial.json`
|
|
@@ -1013,6 +1086,15 @@ locales y te deja elegir con las flechas:
|
|
|
1013
1086
|
| `--url-defecto` | `http://localhost:5000` | URL para abrir el navegador si Flutter no reporta una |
|
|
1014
1087
|
| `--comando-test` | `"flutter test"` | Comando del bucle de pruebas |
|
|
1015
1088
|
| `--max-iteraciones` | `3` | Iteraciones máximas del bucle |
|
|
1089
|
+
| `--validar` | on | Valida la sintaxis del código antes de guardar en el editor propio |
|
|
1090
|
+
| `--no-validar-sintaxis` | off | Desactiva la validación de sintaxis del editor propio |
|
|
1091
|
+
| `--max-intentos-validacion` | `3` | Intentos de validación de sintaxis antes de cancelar la edición |
|
|
1092
|
+
| `--asesor` (`--sugerir`) | off | Asesor proactivo: analiza y sugiere mejoras sin modificar código |
|
|
1093
|
+
| `--asesor-auto` | off | Aplica automáticamente las mejoras seguras del asesor |
|
|
1094
|
+
| `--asesor-umbral` | `20` | Líneas máximas por función para el asesor |
|
|
1095
|
+
|
|
1096
|
+
> **Nota:** el flag negativo de validación de sintaxis se llama `--no-validar-sintaxis`
|
|
1097
|
+
> porque `--no-validar` ya existe como alias de `--iniciar-proyecto`.
|
|
1016
1098
|
|
|
1017
1099
|
---
|
|
1018
1100
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# SnapContext
|
|
2
2
|
|
|
3
|
-

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

|
|
@@ -154,8 +154,81 @@ snapcontext "corregir tipado en login" --editor propio --modo-edicion parche
|
|
|
154
154
|
|
|
155
155
|
# Refactorización estructural con AST
|
|
156
156
|
snapcontext "renombrar la variable 'usuario' a 'cuenta'" --editor propio --modo-edicion ast
|
|
157
|
+
|
|
158
|
+
# Validación de sintaxis desactivada (comportamiento previo a v3.4.0)
|
|
159
|
+
snapcontext "corregir tipado" --editor propio --no-validar-sintaxis
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Validación de sintaxis antes de guardar (v3.4.0)
|
|
163
|
+
|
|
164
|
+
Cuando uses `--editor propio`, SnapContext **valida la sintaxis** del código que
|
|
165
|
+
ha generado la IA **antes de escribirlo en disco**. Si la validación falla, el
|
|
166
|
+
cambio se rechaza y se le envía el error al proveedor para que lo corrija,
|
|
167
|
+
repitiéndose hasta `--max-intentos-validacion` intentos (por defecto 3). Si tras
|
|
168
|
+
esos intentos el código sigue roto, se **cancela la edición** y el archivo queda
|
|
169
|
+
intacto.
|
|
170
|
+
|
|
171
|
+
- Se detecta el lenguaje del archivo y se usa el validador adecuado:
|
|
172
|
+
Python (`py_compile`), JavaScript/TypeScript (`node --check`), Dart
|
|
173
|
+
(`dart analyze` → `dart format`), Go (`go build -n` → `gofmt -e`), Rust
|
|
174
|
+
(`rustc --parse-only`), Java (`javac -Xlint:none`), C/C++ (`gcc`/`clang
|
|
175
|
+
-fsyntax-only`). Si no hay validador o comando disponible, se omite la
|
|
176
|
+
validación (solo aviso en logs con `--depurar`).
|
|
177
|
+
- La validación se hace sobre un **archivo temporal**, nunca toca el original.
|
|
178
|
+
- Flag `--validar` (por defecto activado) / `--no-validar-sintaxis` para
|
|
179
|
+
desactivarla. Nota: el nombre usa `--no-validar-sintaxis` porque
|
|
180
|
+
`--no-validar` ya es alias de `--iniciar-proyecto`.
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
# Desactivar la validación por completo (comportamiento anterior)
|
|
184
|
+
snapcontext "..." --editor propio --no-validar-sintaxis
|
|
185
|
+
|
|
186
|
+
# Ajustar el número de reintentos
|
|
187
|
+
snapcontext "..." --editor propio --max-intentos-validacion 5
|
|
157
188
|
```
|
|
158
189
|
|
|
190
|
+
### 🧠 Asesor de código proactivo (v3.5.0)
|
|
191
|
+
|
|
192
|
+
SnapContext no solo ejecuta órdenes: también **analiza tu código por su cuenta**
|
|
193
|
+
y te sugiere mejoras (refactorizaciones, deuda técnica, optimizaciones).
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
# Análisis informativo (nunca modifica código)
|
|
197
|
+
snapcontext --asesor
|
|
198
|
+
snapcontext --sugerir # alias
|
|
199
|
+
|
|
200
|
+
# Aplicar automáticamente las mejoras seguras (renombrar símbolos);
|
|
201
|
+
# cada cambio se valida antes de guardarse y se descarta si rompe la sintaxis
|
|
202
|
+
snapcontext --asesor-auto
|
|
203
|
+
|
|
204
|
+
# Ajustar la sensibilidad del detector de funciones largas (defecto: 20)
|
|
205
|
+
snapcontext --asesor --asesor-umbral 30
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
**Qué detecta:**
|
|
209
|
+
|
|
210
|
+
- 🔴 **Prioridad alta**: `except:` desnudos (capturan todo sin querer).
|
|
211
|
+
- 🟡 **Prioridad media**: funciones largas (> 20 líneas), clases con demasiadas
|
|
212
|
+
responsabilidades (> 10 métodos), bloques de código duplicados entre archivos,
|
|
213
|
+
comparaciones `== None` y patrones obsoletos (`.has_key()`, Python 2).
|
|
214
|
+
- 🔵 **Prioridad baja**: nombres poco descriptivos (`d`, `tmp`...) con una
|
|
215
|
+
sugerencia concreta de renombrado.
|
|
216
|
+
|
|
217
|
+
Cada sugerencia incluye descripción, ubicación `archivo:línea`, solución
|
|
218
|
+
propuesta y prioridad. Los umbrales se personalizan en
|
|
219
|
+
`~/.snapcontext/config.json`:
|
|
220
|
+
|
|
221
|
+
```json
|
|
222
|
+
{ "asesor": { "funcion_larga": 30, "clase_metodos": 15, "duplicado_lineas": 8 } }
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
El asesor también está disponible:
|
|
226
|
+
|
|
227
|
+
- **En el chat**: comando `/asesor` (o `/sugerir`) dentro de `--chat`.
|
|
228
|
+
- **En el planificador**: paso `{"accion": "asesor", "descripcion": "..."}` —
|
|
229
|
+
cada sugerencia se presenta para aceptarla o rechazarla individualmente.
|
|
230
|
+
- **En la web**: acción *Asesor* que muestra las sugerencias en un panel.
|
|
231
|
+
|
|
159
232
|
## 🧭 Modos y alias
|
|
160
233
|
|
|
161
234
|
| Modo | Comando |
|
|
@@ -430,7 +503,7 @@ snapcontext --plan "..." # commits '
|
|
|
430
503
|
(requiere `ANTHROPIC_API_KEY` y `pip install snapcontext[anthropic]`). Modelo por
|
|
431
504
|
defecto: `claude-3-5-sonnet-20241022`.
|
|
432
505
|
- **Modo chat interactivo**: `snapcontext --chat` abre un REPL con los comandos
|
|
433
|
-
`/salir`, `/archivos`, `/limpiar`, `/seleccion <consulta>`,
|
|
506
|
+
`/salir`, `/archivos`, `/limpiar`, `/seleccion <consulta>`, `/asesor`,
|
|
434
507
|
`/provider <proveedor>`, `/historial` y `/ayuda`. Cualquier otro texto se envía
|
|
435
508
|
al proveedor actual manteniendo la conversación.
|
|
436
509
|
- **Memoria persistente**: cada tarea se guarda en `~/.snapcontext/historial.json`
|
|
@@ -968,6 +1041,15 @@ locales y te deja elegir con las flechas:
|
|
|
968
1041
|
| `--url-defecto` | `http://localhost:5000` | URL para abrir el navegador si Flutter no reporta una |
|
|
969
1042
|
| `--comando-test` | `"flutter test"` | Comando del bucle de pruebas |
|
|
970
1043
|
| `--max-iteraciones` | `3` | Iteraciones máximas del bucle |
|
|
1044
|
+
| `--validar` | on | Valida la sintaxis del código antes de guardar en el editor propio |
|
|
1045
|
+
| `--no-validar-sintaxis` | off | Desactiva la validación de sintaxis del editor propio |
|
|
1046
|
+
| `--max-intentos-validacion` | `3` | Intentos de validación de sintaxis antes de cancelar la edición |
|
|
1047
|
+
| `--asesor` (`--sugerir`) | off | Asesor proactivo: analiza y sugiere mejoras sin modificar código |
|
|
1048
|
+
| `--asesor-auto` | off | Aplica automáticamente las mejoras seguras del asesor |
|
|
1049
|
+
| `--asesor-umbral` | `20` | Líneas máximas por función para el asesor |
|
|
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`.
|
|
971
1053
|
|
|
972
1054
|
---
|
|
973
1055
|
|
|
@@ -232,9 +232,55 @@ class AgenteEditorPropio:
|
|
|
232
232
|
return sc._editor_ast(archivo, tarea, directorio=directorio,
|
|
233
233
|
proveedor=proveedor, modelo=modelo)
|
|
234
234
|
|
|
235
|
+
@staticmethod
|
|
236
|
+
def _aplicar_parche_preview(parche: str, contenido_actual: str):
|
|
237
|
+
"""Aplica ``parche`` a ``contenido_actual`` **en memoria** para poder
|
|
238
|
+
validar la sintaxis del resultado sin tocar el archivo real.
|
|
239
|
+
|
|
240
|
+
Reutiliza la segmentación de hunks de ``_parsear_hunks`` y aplica cada
|
|
241
|
+
hunk con tolerancia a pequeños desfases. Devuelve ``(resultado, n)``
|
|
242
|
+
donde ``n`` es el número de hunks aplicados (0 ⇒ no se pudo reproducir).
|
|
243
|
+
"""
|
|
244
|
+
import snapcontext as sc
|
|
245
|
+
|
|
246
|
+
resultado = (contenido_actual or "").splitlines()
|
|
247
|
+
desplazamiento = 0
|
|
248
|
+
aplicados = 0
|
|
249
|
+
for inicio_orig, cambios in sc._parsear_hunks(parche):
|
|
250
|
+
base = max(inicio_orig - 1 + desplazamiento, 0)
|
|
251
|
+
n_borrados = sum(1 for marca, _ in cambios if marca != "+")
|
|
252
|
+
nuevos = [texto for marca, texto in cambios if marca != "-"]
|
|
253
|
+
|
|
254
|
+
posicion = -1
|
|
255
|
+
for delta in (0, 1, -1, 2, -2, 3, -3, 5, -5, 10, -10):
|
|
256
|
+
candidato = base + delta
|
|
257
|
+
if candidato < 0 or candidato > len(resultado):
|
|
258
|
+
continue
|
|
259
|
+
idx = candidato
|
|
260
|
+
encaja = True
|
|
261
|
+
for marca, texto in cambios:
|
|
262
|
+
if marca == "+":
|
|
263
|
+
continue
|
|
264
|
+
if idx >= len(resultado) or resultado[idx] != texto:
|
|
265
|
+
encaja = False
|
|
266
|
+
break
|
|
267
|
+
idx += 1
|
|
268
|
+
if encaja:
|
|
269
|
+
posicion = candidato
|
|
270
|
+
break
|
|
271
|
+
if posicion < 0:
|
|
272
|
+
continue
|
|
273
|
+
|
|
274
|
+
resultado[posicion:posicion + n_borrados] = nuevos
|
|
275
|
+
desplazamiento += len(nuevos) - n_borrados
|
|
276
|
+
aplicados += 1
|
|
277
|
+
|
|
278
|
+
return "\n".join(resultado) + "\n", aplicados
|
|
279
|
+
|
|
235
280
|
def _aplicar_modo_parche(self, archivo: str, mensaje: str,
|
|
236
281
|
contenido_actual: str, modelo: Optional[str],
|
|
237
|
-
directorio: str
|
|
282
|
+
directorio: str, validar: bool = True,
|
|
283
|
+
max_intentos_validacion: int = 3) -> bool:
|
|
238
284
|
"""Intenta editar `archivo` pidiendo un parche unificado al proveedor."""
|
|
239
285
|
import snapcontext as sc
|
|
240
286
|
|
|
@@ -243,57 +289,134 @@ class AgenteEditorPropio:
|
|
|
243
289
|
lenguaje = sc._lenguaje_archivo(archivo, contenido_actual) or "?"
|
|
244
290
|
num_lineas = (contenido_actual.count("\n") + 1
|
|
245
291
|
if contenido_actual else 0)
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
292
|
+
max_val = max(1, int(max_intentos_validacion))
|
|
293
|
+
|
|
294
|
+
error_msj = ""
|
|
295
|
+
for intento in range(1, max_val + 1):
|
|
296
|
+
prompt = (
|
|
297
|
+
f"Genera un parche unificado (unified diff) que modifique el archivo "
|
|
298
|
+
f"para cumplir con la tarea con precisión máxima.\n"
|
|
299
|
+
f"El parche debe ser aplicable con 'patch -p1' o 'git apply' "
|
|
300
|
+
f"(encabezados --- a/{archivo} y +++ b/{archivo}).\n\n"
|
|
301
|
+
f"Tarea: {mensaje}\n"
|
|
302
|
+
f"Archivo: {archivo} (lenguaje: {lenguaje}, {num_lineas} líneas)\n\n"
|
|
303
|
+
f"Instrucciones:\n"
|
|
304
|
+
f"- Incluye suficiente contexto (3 líneas) para anclar cada hunk.\n"
|
|
305
|
+
f"- Conserva el estilo existente (indentación, comillas).\n"
|
|
306
|
+
f"- Modifica SOLO lo necesario; no reorganices el resto del archivo.\n"
|
|
307
|
+
)
|
|
308
|
+
if error_msj:
|
|
309
|
+
prompt += (
|
|
310
|
+
f"\nTu intento anterior produjo un error de sintaxis que debes "
|
|
311
|
+
f"corregir:\n{error_msj}\n\n"
|
|
312
|
+
f"Mantén el objetivo de la tarea pero arregla ese error.\n"
|
|
313
|
+
)
|
|
314
|
+
prompt += (
|
|
315
|
+
f"\nContenido actual completo:\n```{lenguaje}\n{contenido_actual}\n```\n\n"
|
|
316
|
+
f"Devuelve ÚNICAMENTE el diff unificado, sin explicaciones ni rodeos."
|
|
317
|
+
)
|
|
318
|
+
respuesta = sc._enviar_al_proveedor(
|
|
319
|
+
proveedor, modelo, [{"role": "user", "content": prompt}])
|
|
320
|
+
diff_limpio = respuesta
|
|
321
|
+
if "--- " in diff_limpio and "+++ " in diff_limpio:
|
|
322
|
+
idx_inicio = diff_limpio.find("--- ")
|
|
323
|
+
diff_limpio = diff_limpio[idx_inicio:]
|
|
324
|
+
if "```" in diff_limpio:
|
|
325
|
+
diff_limpio = diff_limpio[:diff_limpio.find("```")]
|
|
326
|
+
if not ("--- " in diff_limpio and "+++ " in diff_limpio
|
|
327
|
+
and "@@" in diff_limpio):
|
|
328
|
+
return False
|
|
329
|
+
|
|
330
|
+
if not validar:
|
|
331
|
+
# v3.3.0: validación previa + resolución de conflictos.
|
|
332
|
+
return self.aplicar_parche(diff_limpio, directorio,
|
|
333
|
+
contenido_esperado=contenido_actual)
|
|
334
|
+
|
|
335
|
+
# v3.4.0: validar la sintaxis del contenido resultante.
|
|
336
|
+
preview, aplicados = self._aplicar_parche_preview(
|
|
337
|
+
diff_limpio, contenido_actual)
|
|
338
|
+
if aplicados == 0:
|
|
339
|
+
# No se pudo reproducir en memoria → se omite la validación.
|
|
340
|
+
return self.aplicar_parche(diff_limpio, directorio,
|
|
341
|
+
contenido_esperado=contenido_actual)
|
|
342
|
+
|
|
343
|
+
sc.info(f"Validando sintaxis de {archivo}...")
|
|
344
|
+
exito, err = sc._validar_sintaxis(archivo, preview, directorio)
|
|
345
|
+
if exito:
|
|
346
|
+
sc.exito("Sintaxis válida.")
|
|
347
|
+
return self.aplicar_parche(diff_limpio, directorio,
|
|
348
|
+
contenido_esperado=contenido_actual)
|
|
349
|
+
|
|
350
|
+
error_msj = err
|
|
351
|
+
if intento < max_val:
|
|
352
|
+
sc.error(
|
|
353
|
+
f"Error de sintaxis: {err}. "
|
|
354
|
+
f"Reintentando ({intento}/{max_val})...")
|
|
355
|
+
else:
|
|
356
|
+
sc.error(
|
|
357
|
+
f"No se pudo validar tras {max_val} intentos. "
|
|
358
|
+
f"Edición cancelada.")
|
|
359
|
+
return False
|
|
272
360
|
return False
|
|
273
361
|
|
|
274
362
|
def _aplicar_modo_sobrescribir(self, archivo: str, mensaje: str,
|
|
275
363
|
contenido_actual: str, modelo: Optional[str],
|
|
276
|
-
directorio: str
|
|
364
|
+
directorio: str, validar: bool = True,
|
|
365
|
+
max_intentos_validacion: int = 3) -> bool:
|
|
277
366
|
"""Sobrescribe `archivo` con el código completo que devuelve el proveedor."""
|
|
278
367
|
import snapcontext as sc
|
|
279
368
|
|
|
280
369
|
pref = sc.cargar_configuracion()
|
|
281
370
|
proveedor = pref.get("provider") or sc.PROVEEDOR_DEFECTO
|
|
282
371
|
lenguaje = sc._lenguaje_archivo(archivo, contenido_actual) or "?"
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
372
|
+
max_val = max(1, int(max_intentos_validacion))
|
|
373
|
+
|
|
374
|
+
error_msj = ""
|
|
375
|
+
nuevo_contenido = ""
|
|
376
|
+
for intento in range(1, max_val + 1):
|
|
377
|
+
prompt = (
|
|
378
|
+
f"Modifica el siguiente archivo ({lenguaje}) para cumplir con la "
|
|
379
|
+
f"tarea, conservando el estilo existente y cambiando solo lo necesario.\n\n"
|
|
380
|
+
f"Tarea: {mensaje}\n"
|
|
381
|
+
f"Archivo: {archivo} (lenguaje: {lenguaje})\n"
|
|
382
|
+
)
|
|
383
|
+
if error_msj:
|
|
384
|
+
prompt += (
|
|
385
|
+
f"\nTu intento anterior produjo un error de sintaxis que debes "
|
|
386
|
+
f"corregir:\n{error_msj}\n\n"
|
|
387
|
+
f"Mantén el objetivo de la tarea pero arregla ese error.\n"
|
|
388
|
+
)
|
|
389
|
+
prompt += (
|
|
390
|
+
f"\nContenido actual completo:\n```{lenguaje}\n{contenido_actual}\n```\n\n"
|
|
391
|
+
f"Devuelve ÚNICAMENTE el código completo resultante, sin explicaciones ni markdown."
|
|
392
|
+
)
|
|
393
|
+
respuesta = sc._enviar_al_proveedor(
|
|
394
|
+
proveedor, modelo, [{"role": "user", "content": prompt}])
|
|
395
|
+
nuevo_contenido = respuesta
|
|
396
|
+
if nuevo_contenido.startswith("```"):
|
|
397
|
+
lineas = nuevo_contenido.splitlines()
|
|
398
|
+
if len(lineas) >= 2 and lineas[-1].startswith("```"):
|
|
399
|
+
nuevo_contenido = "\n".join(lineas[1:-1])
|
|
400
|
+
|
|
401
|
+
if not validar:
|
|
402
|
+
break
|
|
403
|
+
|
|
404
|
+
sc.info(f"Validando sintaxis de {archivo}...")
|
|
405
|
+
exito, err = sc._validar_sintaxis(archivo, nuevo_contenido, directorio)
|
|
406
|
+
if exito:
|
|
407
|
+
sc.exito("Sintaxis válida.")
|
|
408
|
+
break
|
|
409
|
+
error_msj = err
|
|
410
|
+
if intento < max_val:
|
|
411
|
+
sc.error(
|
|
412
|
+
f"Error de sintaxis: {err}. "
|
|
413
|
+
f"Reintentando ({intento}/{max_val})...")
|
|
414
|
+
else:
|
|
415
|
+
sc.error(
|
|
416
|
+
f"No se pudo validar tras {max_val} intentos. "
|
|
417
|
+
f"Edición cancelada.")
|
|
418
|
+
return False
|
|
419
|
+
|
|
297
420
|
return self.sobrescribir(archivo, nuevo_contenido, directorio)
|
|
298
421
|
|
|
299
422
|
def ejecutar(
|
|
@@ -303,6 +426,8 @@ class AgenteEditorPropio:
|
|
|
303
426
|
directorio: str = ".",
|
|
304
427
|
modo_edicion: str = "auto",
|
|
305
428
|
modelo: Optional[str] = None,
|
|
429
|
+
validar: bool = True,
|
|
430
|
+
max_intentos_validacion: int = 3,
|
|
306
431
|
) -> bool:
|
|
307
432
|
"""Aplica las modificaciones para `archivos` con el `mensaje` especificado.
|
|
308
433
|
|
|
@@ -353,10 +478,14 @@ class AgenteEditorPropio:
|
|
|
353
478
|
str(raiz), modelo)
|
|
354
479
|
elif estrategia == "parche":
|
|
355
480
|
conseguido = self._aplicar_modo_parche(
|
|
356
|
-
arch, mensaje, contenido_actual, modelo, str(raiz)
|
|
481
|
+
arch, mensaje, contenido_actual, modelo, str(raiz),
|
|
482
|
+
validar=validar,
|
|
483
|
+
max_intentos_validacion=max_intentos_validacion)
|
|
357
484
|
elif estrategia == "sobrescribir":
|
|
358
485
|
conseguido = self._aplicar_modo_sobrescribir(
|
|
359
|
-
arch, mensaje, contenido_actual, modelo, str(raiz)
|
|
486
|
+
arch, mensaje, contenido_actual, modelo, str(raiz),
|
|
487
|
+
validar=validar,
|
|
488
|
+
max_intentos_validacion=max_intentos_validacion)
|
|
360
489
|
else:
|
|
361
490
|
conseguido = False
|
|
362
491
|
except Exception as exc:
|
|
@@ -539,4 +668,40 @@ class AgenteAprendizaje:
|
|
|
539
668
|
"""Lista los skills almacenados en la memoria persistente."""
|
|
540
669
|
import snapcontext as sc
|
|
541
670
|
|
|
542
|
-
return sc._skill_listar(incluir_archivados=incluir_archivados)
|
|
671
|
+
return sc._skill_listar(incluir_archivados=incluir_archivados)
|
|
672
|
+
|
|
673
|
+
|
|
674
|
+
class AgenteAsesor:
|
|
675
|
+
"""Asesor de código proactivo (v3.5.0).
|
|
676
|
+
|
|
677
|
+
Analiza el proyecto sin que el usuario lo pida explícitamente y propone
|
|
678
|
+
mejoras (refactorizaciones, deuda técnica, patrones obsoletos...). Por
|
|
679
|
+
defecto solo informa; las sugerencias marcadas como seguras pueden
|
|
680
|
+
aplicarse con ``--asesor-auto`` y siempre se validan antes de guardarse.
|
|
681
|
+
"""
|
|
682
|
+
|
|
683
|
+
def analizar(self, directorio: str = ".",
|
|
684
|
+
umbral_funcion: Optional[int] = None) -> List[dict]:
|
|
685
|
+
"""Ejecuta el análisis estático y devuelve la lista de sugerencias."""
|
|
686
|
+
import snapcontext as sc
|
|
687
|
+
|
|
688
|
+
return sc._asesor_analizar(directorio, umbral_funcion=umbral_funcion)
|
|
689
|
+
|
|
690
|
+
def mostrar(self, sugerencias: List[dict]) -> None:
|
|
691
|
+
"""Muestra las sugerencias en la CLI con colores."""
|
|
692
|
+
import snapcontext as sc
|
|
693
|
+
|
|
694
|
+
sc._asesor_mostrar(sugerencias)
|
|
695
|
+
|
|
696
|
+
def aplicar_automaticas(self, sugerencias: List[dict],
|
|
697
|
+
directorio: str = ".") -> int:
|
|
698
|
+
"""Aplica las mejoras seguras (auto=True); devuelve cuántas se aplicaron."""
|
|
699
|
+
import snapcontext as sc
|
|
700
|
+
|
|
701
|
+
return sc._asesor_aplicar_automaticas(sugerencias, directorio)
|
|
702
|
+
|
|
703
|
+
def ejecutar(self, args) -> int:
|
|
704
|
+
"""Flujo completo del modo CLI (--asesor / --asesor-auto)."""
|
|
705
|
+
import snapcontext as sc
|
|
706
|
+
|
|
707
|
+
return sc._ejecutar_asesor(args)
|
|
@@ -21,7 +21,8 @@ import sys
|
|
|
21
21
|
from typing import List, Optional, Tuple
|
|
22
22
|
|
|
23
23
|
from agentes import (AgenteContexto, AgenteEditor, AgenteEditorAST,
|
|
24
|
-
AgenteEditorPropio, AgenteAprendizaje, AgenteTester
|
|
24
|
+
AgenteEditorPropio, AgenteAprendizaje, AgenteTester,
|
|
25
|
+
AgenteAsesor)
|
|
25
26
|
|
|
26
27
|
# Centinela diferenciado del None de "aborto": _planificar lo devuelve cuando
|
|
27
28
|
# el pipeline termina de forma exitosa y temprana (p. ej. --vista-previa).
|
|
@@ -44,6 +45,7 @@ class Orquestador:
|
|
|
44
45
|
self.agente_editor_ast = AgenteEditorAST()
|
|
45
46
|
self.agente_tester = AgenteTester()
|
|
46
47
|
self.agente_aprendizaje = AgenteAprendizaje()
|
|
48
|
+
self.agente_asesor = AgenteAsesor()
|
|
47
49
|
self.evento_callback = evento_callback
|
|
48
50
|
|
|
49
51
|
def _on_evento(self, evento: dict) -> None:
|
|
@@ -190,6 +192,9 @@ class Orquestador:
|
|
|
190
192
|
directorio=str(raiz),
|
|
191
193
|
modo_edicion=modo_ed,
|
|
192
194
|
modelo=getattr(args, "modelo", None),
|
|
195
|
+
validar=getattr(args, "validar", True),
|
|
196
|
+
max_intentos_validacion=getattr(
|
|
197
|
+
args, "max_intentos_validacion", 3),
|
|
193
198
|
)
|
|
194
199
|
self._emitir_tipo("editor", accion="fin", ok=ok)
|
|
195
200
|
else:
|
|
@@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
|
|
|
6
6
|
# Nombre verificado disponible en PyPI (18/08/2026): pypi.org/pypi/snapcontext → 404.
|
|
7
7
|
# Alternativas si algún día estuviera ocupado: "snapcontext-cli" o "snapcontext-tool".
|
|
8
8
|
name = "snapcontext"
|
|
9
|
-
version = "3.
|
|
9
|
+
version = "3.5.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"
|