feat(ml): cierre del bucle de mejora comfyui-skill (genera→juzga→bump)

Tres funciones nuevas que cierran el lazo skill→generación→juicio→promoción
del grupo comfyui-skill (issue 0087):

- comfyui_bump_skill_version (impura): promueve una versión nueva SOLO si el
  score del panel-juez sube (gate objetivo). Snapshot versions/vN.json
  pre-mutación, deep-merge de recipe_patch, semver↑, línea en growth_log.jsonl.
  force=True salta el gate. No usa datetime.now().
- comfyui_update_skill_score (impura): media incremental de score_mean/score_n
  reescribiendo recipe.json in-place (sin snapshot ni growth_log).
- comfyui_generate_with_skill_oneshot (pipeline): one-shot load→build→submit→
  wait→fetch→judge→score_mean. recipe_patch prueba variantes sin guardar score.
  Compone 7 funciones del registry.

Tests offline: 11 passed (gate, semver, deep-merge, media incremental, errores).
Página madre docs/capabilities/comfyui-skill.md: +3 funciones, sección "Bucle de
mejora" con diagrama, fronteras de scoring actualizadas.

Demo real verificada: skill seed portrait_cinematic_sd15 (SD1.5) generó imagen
SFW real, el panel la juzgó, una variante puntuó más alto (4.787 > 4.7276) y el
gate promovió v1.0.0→v1.1.0 con el judge_run_id como evidencia.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-24 15:09:33 +02:00
parent 974cc06bc7
commit bcf731275e
9 changed files with 1046 additions and 3 deletions
@@ -0,0 +1,90 @@
---
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`.