snapcontext 4.0.0__tar.gz → 4.2.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-4.0.0/snapcontext.egg-info → snapcontext-4.2.0}/PKG-INFO +75 -5
- {snapcontext-4.0.0 → snapcontext-4.2.0}/README.md +74 -4
- {snapcontext-4.0.0 → snapcontext-4.2.0}/agentes.py +206 -56
- {snapcontext-4.0.0 → snapcontext-4.2.0}/orquestador.py +3 -0
- {snapcontext-4.0.0 → snapcontext-4.2.0}/pyproject.toml +1 -1
- {snapcontext-4.0.0 → snapcontext-4.2.0/snapcontext.egg-info}/PKG-INFO +75 -5
- {snapcontext-4.0.0 → snapcontext-4.2.0}/snapcontext.egg-info/SOURCES.txt +2 -0
- {snapcontext-4.0.0 → snapcontext-4.2.0}/snapcontext.py +307 -30
- {snapcontext-4.0.0 → snapcontext-4.2.0}/tests/test_auto_017.py +1 -1
- {snapcontext-4.0.0 → snapcontext-4.2.0}/tests/test_diagnostico_310.py +1 -1
- {snapcontext-4.0.0 → snapcontext-4.2.0}/tests/test_diff_editor_210.py +1 -1
- snapcontext-4.2.0/tests/test_editor_410.py +179 -0
- {snapcontext-4.0.0 → snapcontext-4.2.0}/tests/test_editor_ast_220.py +1 -1
- {snapcontext-4.0.0 → snapcontext-4.2.0}/tests/test_editor_propio_200.py +5 -3
- {snapcontext-4.0.0 → snapcontext-4.2.0}/tests/test_exe_150.py +1 -1
- {snapcontext-4.0.0 → snapcontext-4.2.0}/tests/test_jetbrains_170.py +1 -1
- {snapcontext-4.0.0 → snapcontext-4.2.0}/tests/test_mcp_014.py +1 -1
- {snapcontext-4.0.0 → snapcontext-4.2.0}/tests/test_mcp_140.py +1 -1
- {snapcontext-4.0.0 → snapcontext-4.2.0}/tests/test_memoria_015.py +1 -1
- {snapcontext-4.0.0 → snapcontext-4.2.0}/tests/test_memoria_300.py +1 -1
- {snapcontext-4.0.0 → snapcontext-4.2.0}/tests/test_permisos_013.py +1 -1
- {snapcontext-4.0.0 → snapcontext-4.2.0}/tests/test_plan_012.py +1 -1
- {snapcontext-4.0.0 → snapcontext-4.2.0}/tests/test_plan_mcp_230.py +1 -1
- snapcontext-4.2.0/tests/test_seguridad_rendimiento_420.py +203 -0
- {snapcontext-4.0.0 → snapcontext-4.2.0}/tests/test_v010.py +1 -1
- {snapcontext-4.0.0 → snapcontext-4.2.0}/tests/test_validacion_130.py +1 -1
- {snapcontext-4.0.0 → snapcontext-4.2.0}/tests/test_vscode_016.py +1 -1
- {snapcontext-4.0.0 → snapcontext-4.2.0}/tests/test_web_160.py +1 -1
- {snapcontext-4.0.0 → snapcontext-4.2.0}/LICENSE +0 -0
- {snapcontext-4.0.0 → snapcontext-4.2.0}/setup.cfg +0 -0
- {snapcontext-4.0.0 → snapcontext-4.2.0}/snapcontext.egg-info/__pycache__/top_level.cpython-313.pyc +0 -0
- {snapcontext-4.0.0 → snapcontext-4.2.0}/snapcontext.egg-info/dependency_links.txt +0 -0
- {snapcontext-4.0.0 → snapcontext-4.2.0}/snapcontext.egg-info/entry_points.txt +0 -0
- {snapcontext-4.0.0 → snapcontext-4.2.0}/snapcontext.egg-info/requires.txt +0 -0
- {snapcontext-4.0.0 → snapcontext-4.2.0}/snapcontext.egg-info/top_level.txt +0 -0
- {snapcontext-4.0.0 → snapcontext-4.2.0}/tests/test_agentes.py +0 -0
- {snapcontext-4.0.0 → snapcontext-4.2.0}/tests/test_api_360.py +0 -0
- {snapcontext-4.0.0 → snapcontext-4.2.0}/tests/test_asesor_350.py +0 -0
- {snapcontext-4.0.0 → snapcontext-4.2.0}/tests/test_bienvenida_311.py +0 -0
- {snapcontext-4.0.0 → snapcontext-4.2.0}/tests/test_deteccion.py +0 -0
- {snapcontext-4.0.0 → snapcontext-4.2.0}/tests/test_editor_330.py +0 -0
- {snapcontext-4.0.0 → snapcontext-4.2.0}/tests/test_embeddings_110.py +0 -0
- {snapcontext-4.0.0 → snapcontext-4.2.0}/tests/test_orquestador.py +0 -0
- {snapcontext-4.0.0 → snapcontext-4.2.0}/tests/test_plugins_400.py +0 -0
- {snapcontext-4.0.0 → snapcontext-4.2.0}/tests/test_validacion_sintaxis_340.py +0 -0
- {snapcontext-4.0.0 → snapcontext-4.2.0}/tests/test_vscode_ts_320.py +0 -0
- {snapcontext-4.0.0 → snapcontext-4.2.0}/tests/test_web_120.py +0 -0
- {snapcontext-4.0.0 → snapcontext-4.2.0}/web/__init__.py +0 -0
- {snapcontext-4.0.0 → snapcontext-4.2.0}/web/app.py +0 -0
- {snapcontext-4.0.0 → snapcontext-4.2.0}/web/static/index.html +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: snapcontext
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.2.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
|

|
|
@@ -178,7 +178,7 @@ carpetas, extensiones y comandos de test por defecto.
|
|
|
178
178
|
|
|
179
179
|
SnapContext incluye su propio editor integrado con soporte de parches unificados
|
|
180
180
|
y edición basada en AST (Árbol Sintáctico) para refactorizaciones precisas:
|
|
181
|
-
- **`--editor propio
|
|
181
|
+
- **`--editor propio`** (por defecto desde v4.1.0): Aplica cambios directamente y crea copias de seguridad automáticas en `~/.snapcontext/backups/`.
|
|
182
182
|
- **`--modo-edicion {auto,parche,sobrescribir,ast}`**:
|
|
183
183
|
- `auto` (por defecto): Genera un parche unificado (`git apply` / `patch`), pero
|
|
184
184
|
para refactorizaciones estructurales intenta primero la edición **AST** y cae a
|
|
@@ -188,7 +188,35 @@ y edición basada en AST (Árbol Sintáctico) para refactorizaciones precisas:
|
|
|
188
188
|
símbolos, insertar imports y recibir el código completo resultante.
|
|
189
189
|
- `parche`: Fuerza la aplicación de parches unificados para ediciones precisas.
|
|
190
190
|
- `sobrescribir`: Sobrescribe el archivo completo.
|
|
191
|
-
- **`--editor aider
|
|
191
|
+
- **`--editor aider`**: Mantiene el flujo de edición con Aider para máxima compatibilidad.
|
|
192
|
+
|
|
193
|
+
### Editor propio (modo por defecto desde v4.1.0)
|
|
194
|
+
|
|
195
|
+
Desde la v4.1.0, **el editor propio es el modo por defecto**, reduciendo la
|
|
196
|
+
dependencia de Aider sin eliminarla (`--editor aider` sigue disponible).
|
|
197
|
+
|
|
198
|
+
**Cadena de auto-reparación**: para cada archivo se intentan las estrategias en
|
|
199
|
+
orden — **AST → Parche → Sobrescritura** — informando cuál está en uso:
|
|
200
|
+
|
|
201
|
+
```
|
|
202
|
+
ℹ Editor propio: usando estrategia AST para 'modulo.py'...
|
|
203
|
+
ℹ Editor propio: usando estrategia PARCHE para 'modulo.py'...
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Si todas fallan, verás un error claro con las estrategias intentadas, el motivo
|
|
207
|
+
y la sugerencia de probar con `--editor aider`. El fallo queda registrado en
|
|
208
|
+
`~/.snapcontext/logs/editor_fallos.log` para depuración.
|
|
209
|
+
|
|
210
|
+
**Resolución interactiva de conflictos**: si un parche no se aplica limpiamente,
|
|
211
|
+
se ofrece elegir entre `[a]plicar de todas formas`, `[v]er el diff`,
|
|
212
|
+
`[r]eintentar con el proveedor de IA` o `[c]ancelar conservando el original`.
|
|
213
|
+
En modo `--auto` no se pregunta: se salta automáticamente a la siguiente
|
|
214
|
+
estrategia. Funciona igual desde la CLI que desde `--chat`.
|
|
215
|
+
|
|
216
|
+
**Optimizado para modelos locales (Ollama)**: si el proveedor es Ollama, el
|
|
217
|
+
editor usa automáticamente **prompts concisos** en sus tres estrategias (menos
|
|
218
|
+
contexto e instrucciones directas). También puedes forzarlos en cualquier
|
|
219
|
+
proveedor con `--modelo-ligero` si sabes que usas un modelo pequeño.
|
|
192
220
|
|
|
193
221
|
```bash
|
|
194
222
|
# Edición precisa mediante diffs unificados
|
|
@@ -274,6 +302,46 @@ El asesor también está disponible:
|
|
|
274
302
|
cada sugerencia se presenta para aceptarla o rechazarla individualmente.
|
|
275
303
|
- **En la web**: acción *Asesor* que muestra las sugerencias en un panel.
|
|
276
304
|
|
|
305
|
+
### 🛡️ Seguridad y rendimiento en el asesor (v4.2.0)
|
|
306
|
+
|
|
307
|
+
Con el nuevo flag `--asesor-profundo`, el asesor añade dos análisis extra a
|
|
308
|
+
las heurísticas básicas de siempre:
|
|
309
|
+
|
|
310
|
+
```bash
|
|
311
|
+
snapcontext --asesor-profundo # heurísticas + seguridad 🔒 + rendimiento ⚡
|
|
312
|
+
snapcontext --asesor # solo heurísticas básicas (como v3.5.0)
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
**🔒 Vulnerabilidades detectadas** (heurísticas propias, sin dependencias
|
|
316
|
+
externas como bandit):
|
|
317
|
+
|
|
318
|
+
| Vulnerabilidad | Ejemplo |
|
|
319
|
+
|---|---|
|
|
320
|
+
| Inyección SQL | `"SELECT ... WHERE id = " + user_id` |
|
|
321
|
+
| Command injection | `os.system("ping " + ip)`, `subprocess(..., shell=True)` |
|
|
322
|
+
| Path traversal | `open("../" + filename)` |
|
|
323
|
+
| Hardcoded secrets | `API_KEY = "sk-..."` en el código |
|
|
324
|
+
| eval/exec inseguros | `eval(user_input)` |
|
|
325
|
+
| XSS | `elemento.innerHTML = entrada` |
|
|
326
|
+
|
|
327
|
+
**⚡ Rendimiento**: bucles anidados O(n²), `range(len(...))`, concatenación de
|
|
328
|
+
cadenas con `+=` dentro de bucles, consultas N+1 del ORM y lectura completa de
|
|
329
|
+
archivos grandes en memoria.
|
|
330
|
+
|
|
331
|
+
Cada hallazgo incluye descripción, `archivo:línea`, solución propuesta y
|
|
332
|
+
prioridad, integrado con el resto de sugerencias del asesor.
|
|
333
|
+
|
|
334
|
+
También disponibles por separado:
|
|
335
|
+
|
|
336
|
+
```bash
|
|
337
|
+
# En el chat
|
|
338
|
+
snapcontext --chat # luego: /seguridad · /rendimiento
|
|
339
|
+
|
|
340
|
+
# En el planificador
|
|
341
|
+
{"accion": "seguridad", "descripcion": "auditar el módulo de pagos"}
|
|
342
|
+
{"accion": "rendimiento", "descripcion": "optimizar consultas"}
|
|
343
|
+
```
|
|
344
|
+
|
|
277
345
|
### 🔌 API pública (v3.6.0)
|
|
278
346
|
|
|
279
347
|
SnapContext expone una **API REST** para interactuar de forma programática
|
|
@@ -392,7 +460,9 @@ Sin plugins instalados, SnapContext funciona exactamente igual que siempre.
|
|
|
392
460
|
| Modo | Comando |
|
|
393
461
|
|------|---------|
|
|
394
462
|
| Tarea | `snapcontext "<consulta>"` (+ `--test-loop`) |
|
|
395
|
-
| Editor propio | `--editor propio` (con backups automáticos) |
|
|
463
|
+
| Editor propio | `--editor propio` (por defecto desde v4.1.0, con backups automáticos) |
|
|
464
|
+
| Aider (opcional) | `--editor aider` (flujo clásico con Aider) |
|
|
465
|
+
| Prompts para modelos pequeños | `--modelo-ligero` (automático con Ollama) |
|
|
396
466
|
| Vista previa / revisión | `--vista-previa` · alias `review` |
|
|
397
467
|
| Chat interactivo | `--chat` |
|
|
398
468
|
| Planificador | `--plan "<tarea>"` |
|
|
@@ -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
|

|
|
@@ -133,7 +133,7 @@ carpetas, extensiones y comandos de test por defecto.
|
|
|
133
133
|
|
|
134
134
|
SnapContext incluye su propio editor integrado con soporte de parches unificados
|
|
135
135
|
y edición basada en AST (Árbol Sintáctico) para refactorizaciones precisas:
|
|
136
|
-
- **`--editor propio
|
|
136
|
+
- **`--editor propio`** (por defecto desde v4.1.0): Aplica cambios directamente y crea copias de seguridad automáticas en `~/.snapcontext/backups/`.
|
|
137
137
|
- **`--modo-edicion {auto,parche,sobrescribir,ast}`**:
|
|
138
138
|
- `auto` (por defecto): Genera un parche unificado (`git apply` / `patch`), pero
|
|
139
139
|
para refactorizaciones estructurales intenta primero la edición **AST** y cae a
|
|
@@ -143,7 +143,35 @@ y edición basada en AST (Árbol Sintáctico) para refactorizaciones precisas:
|
|
|
143
143
|
símbolos, insertar imports y recibir el código completo resultante.
|
|
144
144
|
- `parche`: Fuerza la aplicación de parches unificados para ediciones precisas.
|
|
145
145
|
- `sobrescribir`: Sobrescribe el archivo completo.
|
|
146
|
-
- **`--editor aider
|
|
146
|
+
- **`--editor aider`**: Mantiene el flujo de edición con Aider para máxima compatibilidad.
|
|
147
|
+
|
|
148
|
+
### Editor propio (modo por defecto desde v4.1.0)
|
|
149
|
+
|
|
150
|
+
Desde la v4.1.0, **el editor propio es el modo por defecto**, reduciendo la
|
|
151
|
+
dependencia de Aider sin eliminarla (`--editor aider` sigue disponible).
|
|
152
|
+
|
|
153
|
+
**Cadena de auto-reparación**: para cada archivo se intentan las estrategias en
|
|
154
|
+
orden — **AST → Parche → Sobrescritura** — informando cuál está en uso:
|
|
155
|
+
|
|
156
|
+
```
|
|
157
|
+
ℹ Editor propio: usando estrategia AST para 'modulo.py'...
|
|
158
|
+
ℹ Editor propio: usando estrategia PARCHE para 'modulo.py'...
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Si todas fallan, verás un error claro con las estrategias intentadas, el motivo
|
|
162
|
+
y la sugerencia de probar con `--editor aider`. El fallo queda registrado en
|
|
163
|
+
`~/.snapcontext/logs/editor_fallos.log` para depuración.
|
|
164
|
+
|
|
165
|
+
**Resolución interactiva de conflictos**: si un parche no se aplica limpiamente,
|
|
166
|
+
se ofrece elegir entre `[a]plicar de todas formas`, `[v]er el diff`,
|
|
167
|
+
`[r]eintentar con el proveedor de IA` o `[c]ancelar conservando el original`.
|
|
168
|
+
En modo `--auto` no se pregunta: se salta automáticamente a la siguiente
|
|
169
|
+
estrategia. Funciona igual desde la CLI que desde `--chat`.
|
|
170
|
+
|
|
171
|
+
**Optimizado para modelos locales (Ollama)**: si el proveedor es Ollama, el
|
|
172
|
+
editor usa automáticamente **prompts concisos** en sus tres estrategias (menos
|
|
173
|
+
contexto e instrucciones directas). También puedes forzarlos en cualquier
|
|
174
|
+
proveedor con `--modelo-ligero` si sabes que usas un modelo pequeño.
|
|
147
175
|
|
|
148
176
|
```bash
|
|
149
177
|
# Edición precisa mediante diffs unificados
|
|
@@ -229,6 +257,46 @@ El asesor también está disponible:
|
|
|
229
257
|
cada sugerencia se presenta para aceptarla o rechazarla individualmente.
|
|
230
258
|
- **En la web**: acción *Asesor* que muestra las sugerencias en un panel.
|
|
231
259
|
|
|
260
|
+
### 🛡️ Seguridad y rendimiento en el asesor (v4.2.0)
|
|
261
|
+
|
|
262
|
+
Con el nuevo flag `--asesor-profundo`, el asesor añade dos análisis extra a
|
|
263
|
+
las heurísticas básicas de siempre:
|
|
264
|
+
|
|
265
|
+
```bash
|
|
266
|
+
snapcontext --asesor-profundo # heurísticas + seguridad 🔒 + rendimiento ⚡
|
|
267
|
+
snapcontext --asesor # solo heurísticas básicas (como v3.5.0)
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
**🔒 Vulnerabilidades detectadas** (heurísticas propias, sin dependencias
|
|
271
|
+
externas como bandit):
|
|
272
|
+
|
|
273
|
+
| Vulnerabilidad | Ejemplo |
|
|
274
|
+
|---|---|
|
|
275
|
+
| Inyección SQL | `"SELECT ... WHERE id = " + user_id` |
|
|
276
|
+
| Command injection | `os.system("ping " + ip)`, `subprocess(..., shell=True)` |
|
|
277
|
+
| Path traversal | `open("../" + filename)` |
|
|
278
|
+
| Hardcoded secrets | `API_KEY = "sk-..."` en el código |
|
|
279
|
+
| eval/exec inseguros | `eval(user_input)` |
|
|
280
|
+
| XSS | `elemento.innerHTML = entrada` |
|
|
281
|
+
|
|
282
|
+
**⚡ Rendimiento**: bucles anidados O(n²), `range(len(...))`, concatenación de
|
|
283
|
+
cadenas con `+=` dentro de bucles, consultas N+1 del ORM y lectura completa de
|
|
284
|
+
archivos grandes en memoria.
|
|
285
|
+
|
|
286
|
+
Cada hallazgo incluye descripción, `archivo:línea`, solución propuesta y
|
|
287
|
+
prioridad, integrado con el resto de sugerencias del asesor.
|
|
288
|
+
|
|
289
|
+
También disponibles por separado:
|
|
290
|
+
|
|
291
|
+
```bash
|
|
292
|
+
# En el chat
|
|
293
|
+
snapcontext --chat # luego: /seguridad · /rendimiento
|
|
294
|
+
|
|
295
|
+
# En el planificador
|
|
296
|
+
{"accion": "seguridad", "descripcion": "auditar el módulo de pagos"}
|
|
297
|
+
{"accion": "rendimiento", "descripcion": "optimizar consultas"}
|
|
298
|
+
```
|
|
299
|
+
|
|
232
300
|
### 🔌 API pública (v3.6.0)
|
|
233
301
|
|
|
234
302
|
SnapContext expone una **API REST** para interactuar de forma programática
|
|
@@ -347,7 +415,9 @@ Sin plugins instalados, SnapContext funciona exactamente igual que siempre.
|
|
|
347
415
|
| Modo | Comando |
|
|
348
416
|
|------|---------|
|
|
349
417
|
| Tarea | `snapcontext "<consulta>"` (+ `--test-loop`) |
|
|
350
|
-
| Editor propio | `--editor propio` (con backups automáticos) |
|
|
418
|
+
| Editor propio | `--editor propio` (por defecto desde v4.1.0, con backups automáticos) |
|
|
419
|
+
| Aider (opcional) | `--editor aider` (flujo clásico con Aider) |
|
|
420
|
+
| Prompts para modelos pequeños | `--modelo-ligero` (automático con Ollama) |
|
|
351
421
|
| Vista previa / revisión | `--vista-previa` · alias `review` |
|
|
352
422
|
| Chat interactivo | `--chat` |
|
|
353
423
|
| Planificador | `--plan "<tarea>"` |
|
|
@@ -150,6 +150,27 @@ class AgenteEditor:
|
|
|
150
150
|
return sc.ejecutar_aider(archivos, mensaje, directorio, opciones)
|
|
151
151
|
|
|
152
152
|
|
|
153
|
+
def _prompts_concisos(proveedor: Optional[str],
|
|
154
|
+
modelo_ligero: bool = False) -> bool:
|
|
155
|
+
"""v4.1.0: ¿usar prompts concisos? Con Ollama o ``--modelo-ligero``."""
|
|
156
|
+
if modelo_ligero:
|
|
157
|
+
return True
|
|
158
|
+
return str(proveedor or "").strip().lower() == "ollama"
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _proveedor_efectivo(proveedor: Optional[str]) -> str:
|
|
162
|
+
"""Resuelve el proveedor: explícito → config.json → defecto."""
|
|
163
|
+
import snapcontext as sc
|
|
164
|
+
|
|
165
|
+
if proveedor:
|
|
166
|
+
return proveedor
|
|
167
|
+
try:
|
|
168
|
+
return sc.cargar_configuracion().get("provider") or \
|
|
169
|
+
sc.PROVEEDOR_DEFECTO
|
|
170
|
+
except Exception:
|
|
171
|
+
return sc.PROVEEDOR_DEFECTO
|
|
172
|
+
|
|
173
|
+
|
|
153
174
|
class AgenteEditorPropio:
|
|
154
175
|
"""Agente Editor Propio: editor integrado de SnapContext.
|
|
155
176
|
|
|
@@ -222,7 +243,8 @@ class AgenteEditorPropio:
|
|
|
222
243
|
return ["parche", "sobrescribir"]
|
|
223
244
|
|
|
224
245
|
def editar_ast(self, archivo: str, tarea: str,
|
|
225
|
-
directorio: str = ".", modelo: Optional[str] = None
|
|
246
|
+
directorio: str = ".", modelo: Optional[str] = None,
|
|
247
|
+
conciso: bool = False) -> bool:
|
|
226
248
|
"""Edita ``archivo`` con base en su AST usando el proveedor de IA."""
|
|
227
249
|
import snapcontext as sc
|
|
228
250
|
|
|
@@ -230,7 +252,8 @@ class AgenteEditorPropio:
|
|
|
230
252
|
proveedor = pref.get("provider") or sc.PROVEEDOR_DEFECTO
|
|
231
253
|
sc.depurar(f"[AgenteEditorPropio] Editando por AST '{archivo}' en '{directorio}'")
|
|
232
254
|
return sc._editor_ast(archivo, tarea, directorio=directorio,
|
|
233
|
-
proveedor=proveedor, modelo=modelo
|
|
255
|
+
proveedor=proveedor, modelo=modelo,
|
|
256
|
+
conciso=conciso)
|
|
234
257
|
|
|
235
258
|
@staticmethod
|
|
236
259
|
def _aplicar_parche_preview(parche: str, contenido_actual: str):
|
|
@@ -280,7 +303,9 @@ class AgenteEditorPropio:
|
|
|
280
303
|
def _aplicar_modo_parche(self, archivo: str, mensaje: str,
|
|
281
304
|
contenido_actual: str, modelo: Optional[str],
|
|
282
305
|
directorio: str, validar: bool = True,
|
|
283
|
-
max_intentos_validacion: int = 3
|
|
306
|
+
max_intentos_validacion: int = 3,
|
|
307
|
+
conciso: bool = False,
|
|
308
|
+
auto: bool = False) -> bool:
|
|
284
309
|
"""Intenta editar `archivo` pidiendo un parche unificado al proveedor."""
|
|
285
310
|
import snapcontext as sc
|
|
286
311
|
|
|
@@ -293,28 +318,38 @@ class AgenteEditorPropio:
|
|
|
293
318
|
|
|
294
319
|
error_msj = ""
|
|
295
320
|
for intento in range(1, max_val + 1):
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
321
|
+
if conciso:
|
|
322
|
+
# v4.1.0: variante reducida para modelos ligeros (Ollama).
|
|
323
|
+
prompt = (
|
|
324
|
+
f"Tarea: {mensaje}\n"
|
|
325
|
+
f"Archivo: {archivo} ({lenguaje}, {num_lineas} líneas)\n\n"
|
|
326
|
+
f"```\n{contenido_actual}\n```\n\n"
|
|
327
|
+
f"Responde SOLO el diff unificado (--- a/… +++ b/… con @@)."
|
|
328
|
+
)
|
|
329
|
+
else:
|
|
330
|
+
prompt = (
|
|
331
|
+
f"Genera un parche unificado (unified diff) que modifique el archivo "
|
|
332
|
+
f"para cumplir con la tarea con precisión máxima.\n"
|
|
333
|
+
f"El parche debe ser aplicable con 'patch -p1' o 'git apply' "
|
|
334
|
+
f"(encabezados --- a/{archivo} y +++ b/{archivo}).\n\n"
|
|
335
|
+
f"Tarea: {mensaje}\n"
|
|
336
|
+
f"Archivo: {archivo} (lenguaje: {lenguaje}, {num_lineas} líneas)\n\n"
|
|
337
|
+
f"Instrucciones:\n"
|
|
338
|
+
f"- Incluye suficiente contexto (3 líneas) para anclar cada hunk.\n"
|
|
339
|
+
f"- Conserva el estilo existente (indentación, comillas).\n"
|
|
340
|
+
f"- Modifica SOLO lo necesario; no reorganices el resto del archivo.\n"
|
|
341
|
+
)
|
|
308
342
|
if error_msj:
|
|
309
343
|
prompt += (
|
|
310
344
|
f"\nTu intento anterior produjo un error de sintaxis que debes "
|
|
311
345
|
f"corregir:\n{error_msj}\n\n"
|
|
312
346
|
f"Mantén el objetivo de la tarea pero arregla ese error.\n"
|
|
313
347
|
)
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
348
|
+
if not conciso:
|
|
349
|
+
prompt += (
|
|
350
|
+
f"\nContenido actual completo:\n```{lenguaje}\n{contenido_actual}\n```\n\n"
|
|
351
|
+
f"Devuelve ÚNICAMENTE el diff unificado, sin explicaciones ni rodeos."
|
|
352
|
+
)
|
|
318
353
|
respuesta = sc._enviar_al_proveedor(
|
|
319
354
|
proveedor, modelo, [{"role": "user", "content": prompt}])
|
|
320
355
|
diff_limpio = respuesta
|
|
@@ -329,40 +364,94 @@ class AgenteEditorPropio:
|
|
|
329
364
|
|
|
330
365
|
if not validar:
|
|
331
366
|
# v3.3.0: validación previa + resolución de conflictos.
|
|
332
|
-
return self.
|
|
333
|
-
|
|
367
|
+
return self._aplicar_con_conflicto(
|
|
368
|
+
archivo, diff_limpio, directorio, contenido_actual,
|
|
369
|
+
preview=None, auto=auto) == "ok"
|
|
334
370
|
|
|
335
371
|
# v3.4.0: validar la sintaxis del contenido resultante.
|
|
336
372
|
preview, aplicados = self._aplicar_parche_preview(
|
|
337
373
|
diff_limpio, contenido_actual)
|
|
338
374
|
if aplicados == 0:
|
|
339
375
|
# No se pudo reproducir en memoria → se omite la validación.
|
|
340
|
-
return self.
|
|
341
|
-
|
|
376
|
+
return self._aplicar_con_conflicto(
|
|
377
|
+
archivo, diff_limpio, directorio, contenido_actual,
|
|
378
|
+
preview=None, auto=auto) == "ok"
|
|
342
379
|
|
|
343
380
|
sc.info(f"Validando sintaxis de {archivo}...")
|
|
344
381
|
exito, err = sc._validar_sintaxis(archivo, preview, directorio)
|
|
345
|
-
if exito:
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
sc.error(
|
|
353
|
-
f"Error de sintaxis: {err}. "
|
|
354
|
-
f"Reintentando ({intento}/{max_val})...")
|
|
355
|
-
else:
|
|
382
|
+
if not exito:
|
|
383
|
+
error_msj = err
|
|
384
|
+
if intento < max_val:
|
|
385
|
+
sc.error(
|
|
386
|
+
f"Error de sintaxis: {err}. "
|
|
387
|
+
f"Reintentando ({intento}/{max_val})...")
|
|
388
|
+
continue
|
|
356
389
|
sc.error(
|
|
357
390
|
f"No se pudo validar tras {max_val} intentos. "
|
|
358
391
|
f"Edición cancelada.")
|
|
359
392
|
return False
|
|
393
|
+
sc.exito("Sintaxis válida.")
|
|
394
|
+
resultado = self._aplicar_con_conflicto(
|
|
395
|
+
archivo, diff_limpio, directorio, contenido_actual,
|
|
396
|
+
preview=preview, auto=auto)
|
|
397
|
+
if resultado == "ok":
|
|
398
|
+
return True
|
|
399
|
+
if resultado == "cancelar":
|
|
400
|
+
return False
|
|
401
|
+
# "reintentar": el usuario pidió reintentar con el proveedor.
|
|
402
|
+
error_msj = ("el parche no se aplicó limpiamente; "
|
|
403
|
+
"genera un diff corregido y completo")
|
|
404
|
+
if intento >= max_val:
|
|
405
|
+
return False
|
|
406
|
+
sc.info(f"Reintentando parche ({intento}/{max_val})...")
|
|
360
407
|
return False
|
|
361
408
|
|
|
409
|
+
def _aplicar_con_conflicto(self, archivo: str, diff: str,
|
|
410
|
+
directorio: str, contenido_actual: str,
|
|
411
|
+
preview: Optional[str] = None,
|
|
412
|
+
auto: bool = False) -> str:
|
|
413
|
+
"""Aplica el parche resolviendo conflictos de forma interactiva.
|
|
414
|
+
|
|
415
|
+
Devuelve ``"ok"``, ``"reintentar"`` o ``"cancelar"``.
|
|
416
|
+
En modo ``--auto`` no pregunta: devuelve el resultado directo para que
|
|
417
|
+
``ejecutar()`` pase a la siguiente estrategia.
|
|
418
|
+
"""
|
|
419
|
+
import snapcontext as sc
|
|
420
|
+
|
|
421
|
+
ok = self.aplicar_parche(diff, directorio,
|
|
422
|
+
contenido_esperado=contenido_actual)
|
|
423
|
+
if ok or auto:
|
|
424
|
+
return "ok" if ok else "reintentar"
|
|
425
|
+
while True:
|
|
426
|
+
opcion = sc._menu_conflicto_parche()
|
|
427
|
+
if opcion == "a":
|
|
428
|
+
# Aplicar de todas formas: usa el preview ya calculado si lo
|
|
429
|
+
# hay; si no, reintenta el parche sin comprobación previa.
|
|
430
|
+
if preview is not None:
|
|
431
|
+
return ("ok" if self.sobrescribir(archivo, preview,
|
|
432
|
+
directorio)
|
|
433
|
+
else "cancelar")
|
|
434
|
+
ok2 = self.aplicar_parche(diff, directorio)
|
|
435
|
+
if ok2:
|
|
436
|
+
return "ok"
|
|
437
|
+
sc.error("El parche sigue sin aplicarse incluso sin "
|
|
438
|
+
"comprobación previa.")
|
|
439
|
+
continue
|
|
440
|
+
if opcion == "v":
|
|
441
|
+
print("--- Diff propuesto ---")
|
|
442
|
+
print(diff)
|
|
443
|
+
print("----------------------")
|
|
444
|
+
continue
|
|
445
|
+
if opcion == "r":
|
|
446
|
+
return "reintentar"
|
|
447
|
+
sc.info(f"Se conserva la versión original de '{archivo}'.")
|
|
448
|
+
return "cancelar"
|
|
449
|
+
|
|
362
450
|
def _aplicar_modo_sobrescribir(self, archivo: str, mensaje: str,
|
|
363
451
|
contenido_actual: str, modelo: Optional[str],
|
|
364
452
|
directorio: str, validar: bool = True,
|
|
365
|
-
max_intentos_validacion: int = 3
|
|
453
|
+
max_intentos_validacion: int = 3,
|
|
454
|
+
conciso: bool = False) -> bool:
|
|
366
455
|
"""Sobrescribe `archivo` con el código completo que devuelve el proveedor."""
|
|
367
456
|
import snapcontext as sc
|
|
368
457
|
|
|
@@ -374,22 +463,32 @@ class AgenteEditorPropio:
|
|
|
374
463
|
error_msj = ""
|
|
375
464
|
nuevo_contenido = ""
|
|
376
465
|
for intento in range(1, max_val + 1):
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
466
|
+
if conciso:
|
|
467
|
+
# v4.1.0: variante reducida para modelos ligeros (Ollama).
|
|
468
|
+
prompt = (
|
|
469
|
+
f"Tarea: {mensaje}\n"
|
|
470
|
+
f"Devuelve SOLO el código completo final de {archivo}:\n"
|
|
471
|
+
f"```\n{contenido_actual}\n```"
|
|
472
|
+
)
|
|
473
|
+
else:
|
|
474
|
+
prompt = (
|
|
475
|
+
f"Modifica el siguiente archivo ({lenguaje}) para cumplir con la "
|
|
476
|
+
f"tarea, conservando el estilo existente y cambiando solo lo necesario.\n\n"
|
|
477
|
+
f"Tarea: {mensaje}\n"
|
|
478
|
+
f"Archivo: {archivo} (lenguaje: {lenguaje})\n"
|
|
479
|
+
)
|
|
480
|
+
if error_msj:
|
|
481
|
+
prompt += (
|
|
482
|
+
f"\nTu intento anterior produjo un error de sintaxis que debes "
|
|
483
|
+
f"corregir:\n{error_msj}\n\n"
|
|
484
|
+
f"Mantén el objetivo de la tarea pero arregla ese error.\n"
|
|
485
|
+
)
|
|
384
486
|
prompt += (
|
|
385
|
-
f"\
|
|
386
|
-
f"
|
|
387
|
-
f"Mantén el objetivo de la tarea pero arregla ese error.\n"
|
|
487
|
+
f"\nContenido actual completo:\n```{lenguaje}\n{contenido_actual}\n```\n\n"
|
|
488
|
+
f"Devuelve ÚNICAMENTE el código completo resultante, sin explicaciones ni markdown."
|
|
388
489
|
)
|
|
389
|
-
|
|
390
|
-
f"\
|
|
391
|
-
f"Devuelve ÚNICAMENTE el código completo resultante, sin explicaciones ni markdown."
|
|
392
|
-
)
|
|
490
|
+
if error_msj and conciso:
|
|
491
|
+
prompt += (f"\n\nError anterior a corregir: {error_msj}")
|
|
393
492
|
respuesta = sc._enviar_al_proveedor(
|
|
394
493
|
proveedor, modelo, [{"role": "user", "content": prompt}])
|
|
395
494
|
nuevo_contenido = respuesta
|
|
@@ -428,6 +527,9 @@ class AgenteEditorPropio:
|
|
|
428
527
|
modelo: Optional[str] = None,
|
|
429
528
|
validar: bool = True,
|
|
430
529
|
max_intentos_validacion: int = 3,
|
|
530
|
+
proveedor: Optional[str] = None,
|
|
531
|
+
modelo_ligero: bool = False,
|
|
532
|
+
auto: bool = False,
|
|
431
533
|
) -> bool:
|
|
432
534
|
"""Aplica las modificaciones para `archivos` con el `mensaje` especificado.
|
|
433
535
|
|
|
@@ -436,12 +538,23 @@ class AgenteEditorPropio:
|
|
|
436
538
|
- modo_edicion == 'ast': edita con base en el AST y cae a sobrescritura si falla.
|
|
437
539
|
- modo_edicion == 'auto': decide por heurística (AST para refactorizaciones,
|
|
438
540
|
parche, y fallback a sobrescritura).
|
|
541
|
+
|
|
542
|
+
v4.1.0:
|
|
543
|
+
- Transparencia: informa la estrategia en uso antes de cada intento.
|
|
544
|
+
- Prompts concisos automáticos con Ollama o ``modelo_ligero=True``.
|
|
545
|
+
- Resolución interactiva de conflictos de parche (omitida con ``auto``).
|
|
546
|
+
- Al fallar todo, error claro con estrategias intentadas + sugerencia
|
|
547
|
+
``--editor aider`` y registro del fallo en ~/.snapcontext/logs/.
|
|
439
548
|
"""
|
|
440
549
|
import snapcontext as sc
|
|
441
550
|
from pathlib import Path
|
|
442
551
|
|
|
443
552
|
raiz = Path(directorio).resolve()
|
|
444
553
|
todo_ok = True
|
|
554
|
+
conciso = _prompts_concisos(
|
|
555
|
+
_proveedor_efectivo(proveedor), modelo_ligero)
|
|
556
|
+
if conciso:
|
|
557
|
+
sc.info("Editor propio: usando prompts concisos (modelo ligero).")
|
|
445
558
|
|
|
446
559
|
# v3.3.0 — Integración con skills: si ya existe un patrón de edición
|
|
447
560
|
# aprendido para esta tarea, se prioriza su estrategia (sin pasar por
|
|
@@ -472,20 +585,25 @@ class AgenteEditorPropio:
|
|
|
472
585
|
cadena.insert(0, estrategia_aprendida)
|
|
473
586
|
conseguido = False
|
|
474
587
|
for estrategia in cadena:
|
|
588
|
+
sc.info(f"Editor propio: usando estrategia "
|
|
589
|
+
f"{estrategia.upper()} para '{arch}'...")
|
|
475
590
|
try:
|
|
476
591
|
if estrategia == "ast":
|
|
477
592
|
conseguido = self.editar_ast(arch, mensaje,
|
|
478
|
-
str(raiz), modelo
|
|
593
|
+
str(raiz), modelo,
|
|
594
|
+
conciso=conciso)
|
|
479
595
|
elif estrategia == "parche":
|
|
480
596
|
conseguido = self._aplicar_modo_parche(
|
|
481
597
|
arch, mensaje, contenido_actual, modelo, str(raiz),
|
|
482
598
|
validar=validar,
|
|
483
|
-
max_intentos_validacion=max_intentos_validacion
|
|
599
|
+
max_intentos_validacion=max_intentos_validacion,
|
|
600
|
+
conciso=conciso, auto=auto)
|
|
484
601
|
elif estrategia == "sobrescribir":
|
|
485
602
|
conseguido = self._aplicar_modo_sobrescribir(
|
|
486
603
|
arch, mensaje, contenido_actual, modelo, str(raiz),
|
|
487
604
|
validar=validar,
|
|
488
|
-
max_intentos_validacion=max_intentos_validacion
|
|
605
|
+
max_intentos_validacion=max_intentos_validacion,
|
|
606
|
+
conciso=conciso)
|
|
489
607
|
else:
|
|
490
608
|
conseguido = False
|
|
491
609
|
except Exception as exc:
|
|
@@ -506,7 +624,22 @@ class AgenteEditorPropio:
|
|
|
506
624
|
except Exception as exc:
|
|
507
625
|
sc.depurar(f"[skills] Refuerzo falló: {exc}")
|
|
508
626
|
else:
|
|
509
|
-
|
|
627
|
+
# v4.1.0: mensaje claro con estrategias intentadas, motivo y
|
|
628
|
+
# sugerencia; además se registra en ~/.snapcontext/logs/.
|
|
629
|
+
estrategias_txt = " → ".join(cadena)
|
|
630
|
+
sc.error(
|
|
631
|
+
f"✖ El editor propio no pudo completar la edición.\n"
|
|
632
|
+
f" Archivo: {arch}\n"
|
|
633
|
+
f" Estrategias intentadas: {estrategias_txt}\n"
|
|
634
|
+
f" Motivo: ninguna estrategia produjo un cambio válido "
|
|
635
|
+
f"(revisa la salida anterior).\n"
|
|
636
|
+
f" Sugerencia: prueba con '--editor aider' o revisa la "
|
|
637
|
+
f"tarea manualmente.")
|
|
638
|
+
try:
|
|
639
|
+
sc._registrar_fallo_editor(arch, mensaje, cadena,
|
|
640
|
+
"sin cambio válido")
|
|
641
|
+
except Exception:
|
|
642
|
+
pass
|
|
510
643
|
todo_ok = False
|
|
511
644
|
|
|
512
645
|
return todo_ok
|
|
@@ -681,11 +814,28 @@ class AgenteAsesor:
|
|
|
681
814
|
"""
|
|
682
815
|
|
|
683
816
|
def analizar(self, directorio: str = ".",
|
|
684
|
-
umbral_funcion: Optional[int] = None
|
|
685
|
-
|
|
817
|
+
umbral_funcion: Optional[int] = None,
|
|
818
|
+
profundo: bool = False) -> List[dict]:
|
|
819
|
+
"""Ejecuta el análisis estático y devuelve la lista de sugerencias.
|
|
820
|
+
|
|
821
|
+
Con ``profundo=True`` añade seguridad 🔒 y rendimiento ⚡ (v4.2.0).
|
|
822
|
+
"""
|
|
823
|
+
import snapcontext as sc
|
|
824
|
+
|
|
825
|
+
return sc._asesor_analizar(directorio, umbral_funcion=umbral_funcion,
|
|
826
|
+
profundo=profundo)
|
|
827
|
+
|
|
828
|
+
def analizar_seguridad(self, directorio: str = ".") -> List[dict]:
|
|
829
|
+
"""Solo vulnerabilidades de seguridad (v4.2.0)."""
|
|
830
|
+
import snapcontext as sc
|
|
831
|
+
|
|
832
|
+
return sc._analizar_seguridad(directorio)
|
|
833
|
+
|
|
834
|
+
def analizar_rendimiento(self, directorio: str = ".") -> List[dict]:
|
|
835
|
+
"""Solo sugerencias de rendimiento (v4.2.0)."""
|
|
686
836
|
import snapcontext as sc
|
|
687
837
|
|
|
688
|
-
return sc.
|
|
838
|
+
return sc._analizar_rendimiento(directorio)
|
|
689
839
|
|
|
690
840
|
def mostrar(self, sugerencias: List[dict]) -> None:
|
|
691
841
|
"""Muestra las sugerencias en la CLI con colores."""
|
|
@@ -207,6 +207,9 @@ class Orquestador:
|
|
|
207
207
|
validar=getattr(args, "validar", True),
|
|
208
208
|
max_intentos_validacion=getattr(
|
|
209
209
|
args, "max_intentos_validacion", 3),
|
|
210
|
+
proveedor=getattr(args, "provider", None),
|
|
211
|
+
modelo_ligero=getattr(args, "modelo_ligero", False),
|
|
212
|
+
auto=getattr(args, "auto", False),
|
|
210
213
|
)
|
|
211
214
|
self._emitir_tipo("editor", accion="fin", ok=ok)
|
|
212
215
|
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 = "4.
|
|
9
|
+
version = "4.2.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"
|