---
name: comfyui_bump_skill_version
kind: function
lang: py
domain: ml
version: "1.0.0"
purity: impure
signature: "def comfyui_bump_skill_version(slug: str, change: str, *, score_before: float, score_after: float, judge_run_id: str = None, recipe_patch: dict = None, force: bool = False, bump: str = \"minor\", library_dir: str = None, timestamp: float = None) -> dict"
description: "Promueve una version nueva de una skill ComfyUI SOLO si el score sube (gate objetivo del bucle de mejora, grupo comfyui-skill). Si score_after <= score_before y no force, rechaza con ok=False sin tocar nada. Si pasa: snapshot pre-mutacion versions/vN.json, aplica recipe_patch (deep-merge) a recipe.json, sube el semver (minor default), y appende a growth_log.jsonl una linea {version,date,change,score_before,score_after,judge_run_id,diff}. library_dir default ~/ComfyUI/skills_library. Slug inexistente -> ok=False. No usa datetime.now (deriva la fecha del timestamp recibido o time.time). Impura: disco. Nunca lanza."
error_type: error_py_core
tags: [comfyui, comfyui-skill, ml, skill, versioning, growth]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
imports: []
params:
  - name: slug
    desc: "Slug de la skill (su carpeta en la libreria)."
  - name: change
    desc: "Descripcion de una linea del cambio que motiva el bump (va al growth_log)."
  - name: score_before
    desc: "Score 0-10 de la version actual (del panel comfyui-judge). keyword-only."
  - name: score_after
    desc: "Score 0-10 de la variante candidata. Debe ser > score_before salvo force=True. keyword-only."
  - name: judge_run_id
    desc: "Identificador de la corrida del juez que justifica el bump (evidencia trazable). keyword-only."
  - name: recipe_patch
    desc: "Dict con los cambios a aplicar sobre la receta (deep-merge). Ej. {'params': {'steps': 32}}. keyword-only."
  - name: force
    desc: "Si True, salta el gate y promueve aunque el score no mejore. keyword-only."
  - name: bump
    desc: "Parte del semver a subir: 'minor' (default), 'major' o 'patch'. keyword-only."
  - name: library_dir
    desc: "Raiz de la libreria. Default ~/ComfyUI/skills_library. keyword-only."
  - name: timestamp
    desc: "Epoch en segundos para la fecha del growth_log; None = time.time(). keyword-only."
output: "dict {ok, slug, old_version, new_version, snapshot_file, growth_entry, recipe_path, error}. ok=False con error si el gate rechaza el bump, si la skill no existe, o si falla la escritura; nunca lanza."
tested: true
tests: [test_semver_helper, test_deep_merge_no_pisa_otras_claves, test_golden_promueve_cuando_score_sube, test_edge_major_y_patch, test_error_gate_bloquea_si_no_mejora, test_error_force_salta_gate, test_error_skill_inexistente]
test_file_path: "python/functions/ml/tests/test_comfyui_bump_skill_version.py"
file_path: "python/functions/ml/comfyui_bump_skill_version.py"
---

# comfyui_bump_skill_version

Pieza de cierre del bucle de mejora del grupo [`comfyui-skill`](../../../docs/capabilities/comfyui-skill.md):
una skill genera, el panel [`comfyui-judge`](../../../docs/capabilities/comfyui-judge.md) la
puntúa, y esta función promueve una versión nueva **solo si el score sube**. El juez decide, no
el humano. Es el "crecimiento por composición" del issue 0087 aplicado a la generación de imágenes.

## Ejemplo

```python
import sys, os
sys.path.insert(0, os.path.join(os.environ["HOME"], "fn_registry", "python", "functions"))
from ml.comfyui_bump_skill_version import comfyui_bump_skill_version

# La variante con steps=32 puntuó 7.4 vs 6.5 de la versión vigente → se promueve.
res = comfyui_bump_skill_version(
    "portrait_cinematic_sd15", "subir steps 28→32 (mejor detalle facial)",
    score_before=6.5, score_after=7.4,
    judge_run_id="judge_abc123", recipe_patch={"params": {"steps": 32}},
)
print(res["old_version"], "→", res["new_version"])   # 1.0.0 → 1.1.0
# recipe.json ahora en 1.1.0 con steps=32; versions/vN.json conserva la 1.0.0;
# growth_log.jsonl tiene una línea con score_before/after + judge_run_id.
```

## Cuando usarla

Tras juzgar una variante de una skill: si el `score` del panel supera al de la versión vigente,
llama a esta función para **promover** la mejora (snapshot + semver + growth_log). Si el score no
sube, NO la llames (o se rechaza por el gate) — esa es justo la garantía del bucle. Pásale el
`judge_run_id` de `comfyui_generate_with_skill_oneshot` como evidencia trazable.

## Gotchas

- **Gate duro**: `score_after <= score_before` sin `force=True` devuelve `{ok:False}` y NO toca
  nada (ni snapshot, ni growth_log, ni versión). Es el comportamiento deseado, no un error.
- **`force=True`** salta el gate (promueve aunque empeore) y marca `growth_entry.forced=True`.
  Úsalo solo para correcciones manuales, no en el bucle automático.
- **`recipe_patch` es deep-merge**: dicts anidados (`params`) se fusionan; listas (`loras`,
  `blocks`) se reemplazan enteras (no se concatenan).
- **Snapshot pre-mutación**: `versions/vN.json` guarda la receta ANTES del patch; el `recipe.json`
  queda con la versión nueva. Recupera la vieja con `comfyui_load_skill(slug, version=N)`.
- **Fecha sin `datetime.now()`**: se deriva de `timestamp` o `time.time()` vía `time.strftime`
  (compatible con entornos que prohíben `datetime.now()`).
- **No genera ni juzga**: solo promueve la receta. Generar + puntuar es trabajo de
  `comfyui_generate_with_skill_oneshot` + `comfyui_judge_image`.
