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.
Files changed (50) hide show
  1. {snapcontext-4.0.0/snapcontext.egg-info → snapcontext-4.2.0}/PKG-INFO +75 -5
  2. {snapcontext-4.0.0 → snapcontext-4.2.0}/README.md +74 -4
  3. {snapcontext-4.0.0 → snapcontext-4.2.0}/agentes.py +206 -56
  4. {snapcontext-4.0.0 → snapcontext-4.2.0}/orquestador.py +3 -0
  5. {snapcontext-4.0.0 → snapcontext-4.2.0}/pyproject.toml +1 -1
  6. {snapcontext-4.0.0 → snapcontext-4.2.0/snapcontext.egg-info}/PKG-INFO +75 -5
  7. {snapcontext-4.0.0 → snapcontext-4.2.0}/snapcontext.egg-info/SOURCES.txt +2 -0
  8. {snapcontext-4.0.0 → snapcontext-4.2.0}/snapcontext.py +307 -30
  9. {snapcontext-4.0.0 → snapcontext-4.2.0}/tests/test_auto_017.py +1 -1
  10. {snapcontext-4.0.0 → snapcontext-4.2.0}/tests/test_diagnostico_310.py +1 -1
  11. {snapcontext-4.0.0 → snapcontext-4.2.0}/tests/test_diff_editor_210.py +1 -1
  12. snapcontext-4.2.0/tests/test_editor_410.py +179 -0
  13. {snapcontext-4.0.0 → snapcontext-4.2.0}/tests/test_editor_ast_220.py +1 -1
  14. {snapcontext-4.0.0 → snapcontext-4.2.0}/tests/test_editor_propio_200.py +5 -3
  15. {snapcontext-4.0.0 → snapcontext-4.2.0}/tests/test_exe_150.py +1 -1
  16. {snapcontext-4.0.0 → snapcontext-4.2.0}/tests/test_jetbrains_170.py +1 -1
  17. {snapcontext-4.0.0 → snapcontext-4.2.0}/tests/test_mcp_014.py +1 -1
  18. {snapcontext-4.0.0 → snapcontext-4.2.0}/tests/test_mcp_140.py +1 -1
  19. {snapcontext-4.0.0 → snapcontext-4.2.0}/tests/test_memoria_015.py +1 -1
  20. {snapcontext-4.0.0 → snapcontext-4.2.0}/tests/test_memoria_300.py +1 -1
  21. {snapcontext-4.0.0 → snapcontext-4.2.0}/tests/test_permisos_013.py +1 -1
  22. {snapcontext-4.0.0 → snapcontext-4.2.0}/tests/test_plan_012.py +1 -1
  23. {snapcontext-4.0.0 → snapcontext-4.2.0}/tests/test_plan_mcp_230.py +1 -1
  24. snapcontext-4.2.0/tests/test_seguridad_rendimiento_420.py +203 -0
  25. {snapcontext-4.0.0 → snapcontext-4.2.0}/tests/test_v010.py +1 -1
  26. {snapcontext-4.0.0 → snapcontext-4.2.0}/tests/test_validacion_130.py +1 -1
  27. {snapcontext-4.0.0 → snapcontext-4.2.0}/tests/test_vscode_016.py +1 -1
  28. {snapcontext-4.0.0 → snapcontext-4.2.0}/tests/test_web_160.py +1 -1
  29. {snapcontext-4.0.0 → snapcontext-4.2.0}/LICENSE +0 -0
  30. {snapcontext-4.0.0 → snapcontext-4.2.0}/setup.cfg +0 -0
  31. {snapcontext-4.0.0 → snapcontext-4.2.0}/snapcontext.egg-info/__pycache__/top_level.cpython-313.pyc +0 -0
  32. {snapcontext-4.0.0 → snapcontext-4.2.0}/snapcontext.egg-info/dependency_links.txt +0 -0
  33. {snapcontext-4.0.0 → snapcontext-4.2.0}/snapcontext.egg-info/entry_points.txt +0 -0
  34. {snapcontext-4.0.0 → snapcontext-4.2.0}/snapcontext.egg-info/requires.txt +0 -0
  35. {snapcontext-4.0.0 → snapcontext-4.2.0}/snapcontext.egg-info/top_level.txt +0 -0
  36. {snapcontext-4.0.0 → snapcontext-4.2.0}/tests/test_agentes.py +0 -0
  37. {snapcontext-4.0.0 → snapcontext-4.2.0}/tests/test_api_360.py +0 -0
  38. {snapcontext-4.0.0 → snapcontext-4.2.0}/tests/test_asesor_350.py +0 -0
  39. {snapcontext-4.0.0 → snapcontext-4.2.0}/tests/test_bienvenida_311.py +0 -0
  40. {snapcontext-4.0.0 → snapcontext-4.2.0}/tests/test_deteccion.py +0 -0
  41. {snapcontext-4.0.0 → snapcontext-4.2.0}/tests/test_editor_330.py +0 -0
  42. {snapcontext-4.0.0 → snapcontext-4.2.0}/tests/test_embeddings_110.py +0 -0
  43. {snapcontext-4.0.0 → snapcontext-4.2.0}/tests/test_orquestador.py +0 -0
  44. {snapcontext-4.0.0 → snapcontext-4.2.0}/tests/test_plugins_400.py +0 -0
  45. {snapcontext-4.0.0 → snapcontext-4.2.0}/tests/test_validacion_sintaxis_340.py +0 -0
  46. {snapcontext-4.0.0 → snapcontext-4.2.0}/tests/test_vscode_ts_320.py +0 -0
  47. {snapcontext-4.0.0 → snapcontext-4.2.0}/tests/test_web_120.py +0 -0
  48. {snapcontext-4.0.0 → snapcontext-4.2.0}/web/__init__.py +0 -0
  49. {snapcontext-4.0.0 → snapcontext-4.2.0}/web/app.py +0 -0
  50. {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.0.0
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
- ![v4.0.0](https://img.shields.io/badge/version-4.0.0-blue.svg)
48
+ ![v4.2.0](https://img.shields.io/badge/version-4.2.0-blue.svg)
49
49
  [![PyPI](https://badge.fury.io/py/snapcontext.svg)](https://pypi.org/project/snapcontext/)
50
50
  [![CI](https://img.shields.io/github/actions/workflow/status/NicolasBruna24/snapcontext/ci.yml?branch=main&label=tests)](https://github.com/NicolasBruña24/snapcontext/actions)
51
51
  ![Python 3.9+](https://img.shields.io/badge/python-3.9%2B-blue.svg)
@@ -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`**: Aplica cambios directamente y crea copias de seguridad automáticas en `~/.snapcontext/backups/`.
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`** (por defecto): Mantiene el flujo de edición con Aider para máxima compatibilidad.
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
- ![v4.0.0](https://img.shields.io/badge/version-4.0.0-blue.svg)
3
+ ![v4.2.0](https://img.shields.io/badge/version-4.2.0-blue.svg)
4
4
  [![PyPI](https://badge.fury.io/py/snapcontext.svg)](https://pypi.org/project/snapcontext/)
5
5
  [![CI](https://img.shields.io/github/actions/workflow/status/NicolasBruna24/snapcontext/ci.yml?branch=main&label=tests)](https://github.com/NicolasBruña24/snapcontext/actions)
6
6
  ![Python 3.9+](https://img.shields.io/badge/python-3.9%2B-blue.svg)
@@ -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`**: Aplica cambios directamente y crea copias de seguridad automáticas en `~/.snapcontext/backups/`.
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`** (por defecto): Mantiene el flujo de edición con Aider para máxima compatibilidad.
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) -> bool:
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) -> bool:
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
- 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
- )
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
- 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
- )
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.aplicar_parche(diff_limpio, directorio,
333
- contenido_esperado=contenido_actual)
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.aplicar_parche(diff_limpio, directorio,
341
- contenido_esperado=contenido_actual)
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
- 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:
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) -> bool:
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
- 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:
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"\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"
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
- 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
- )
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
- sc.error(f"[AgenteEditorPropio] No se pudo editar '{arch}'.")
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) -> List[dict]:
685
- """Ejecuta el análisis estático y devuelve la lista de sugerencias."""
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._asesor_analizar(directorio, umbral_funcion=umbral_funcion)
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.0.0"
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"