--- 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`.