catálogo objetivo P4: la cola de la granja pasa a ser derivada del grafo
drenar.py parte la deuda en ONDAS topológicas (una receta sólo espera por deps que también estén en deuda; las selladas ya están en el store). Onda 1 = construible ya; dentro de cada onda, orden por `unblocks` desc. Da lo que una lista plana no da: el orden correcto, la PROFUNDIDAD real de la cadena (pasos secuenciales = el reloj de pared) y qué paraleliza sin pisarse. base 0 en deuda imagen completa cli 0 en deuda imagen completa escritorio-kde 77 en deuda 12 ondas onda 1 = 1 receta: qtbase escritorio-mirada 4 en deuda 2 ondas onda 1 = 3 HALLAZGO: el escritorio KDE está serializado detrás de UNA receta. qtbase destraba 117 nodos y es lo único de la onda 1 — hasta que no esté sellada no hay nada que paralelizar, por más workers que se enciendan. Cambia la pregunta de "¿cuántas faltan?" (77, poco informativo) a "¿cuál es el camino crítico?". Dos correcciones salidas de mirar la salida: · el grafo se elige por la `cola` declarada en targets.toml, NO por cuál tiene más nodos: incoming-kde SOMBREA recetas canónicas, así que medir escritorio-mirada contra el grafo KDE medía una imagen que nadie construye (3 en deuda en vez de 4). El mismo atajo estaba en seed-graph.py --frontera; corregido en los dos. · regla de la fuente única implementada en deps_de(): manda la receta si existe, la semilla sólo si el nodo es `wanted`, marcando procedencia en la salida. Enchufado al latido: cosecha-cron regenera docs/state/drenaje.json junto al grafo y lo commitea ⇒ cola derivada y versionada, el git diff entre ciclos muestra qué salió de la deuda. NO lanza builds: eso cuesta € y sigue siendo decisión explícita (farm-up + campana-deuda.sh). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -191,6 +191,46 @@ mismo es la lista de lo que falta.
|
||||
Orden de ataque por perfil: topológico, priorizado por `unblocks` — ya se calcula en
|
||||
`build-state.py:151-157`, sólo hay que incluir los nodos `wanted`.
|
||||
|
||||
#### P4 — RESULTADO MEDIDO (2026-07-22)
|
||||
|
||||
`scripts/drenar.py`. No lista "lo que falta" en un montón: lo parte en **ondas topológicas** sobre el
|
||||
subgrafo *en deuda* (una receta sólo espera por deps que también estén en deuda; las selladas ya
|
||||
están en el store y no bloquean). Onda 1 = construible ya. Dentro de cada onda, orden por `unblocks`
|
||||
desc: primero lo que libera más.
|
||||
|
||||
Eso da tres cosas que una lista plana no da: el orden correcto, **la profundidad real de la cadena**
|
||||
—cuántos pasos secuenciales faltan, que es lo que manda en el reloj de pared— y qué se puede
|
||||
paralelizar sin pisarse.
|
||||
|
||||
| perfil | en deuda | ondas | onda 1 |
|
||||
|---|---|---|---|
|
||||
| base | 0 | — | imagen completa |
|
||||
| cli | 0 | — | imagen completa |
|
||||
| **escritorio-kde** | **77** | **12** | **1 receta: `qtbase`** |
|
||||
| escritorio-mirada | 4 | 2 | 3 recetas |
|
||||
|
||||
**El hallazgo: el escritorio KDE está serializado detrás de una sola receta.** `qtbase` destraba 117
|
||||
nodos y es lo único de la onda 1 — hasta que no esté sellada, ningún otro trabajo del escritorio
|
||||
puede empezar, y por más workers que se enciendan no hay nada que paralelizar. Las 12 ondas son el
|
||||
piso de pasos secuenciales. Eso cambia la pregunta de "¿cuántas recetas faltan?" (77, poco
|
||||
informativo) a "¿cuál es el camino crítico?" (qtbase → qttools/qtdeclarative → los KF6 → Plasma).
|
||||
|
||||
Dos correcciones que salieron de mirar la salida:
|
||||
|
||||
- **El grafo se elige por la `cola` que el perfil declara, no por cuál tiene más nodos.** La cola
|
||||
`incoming-kde` SOMBREA recetas canónicas, así que medir `escritorio-mirada` contra el grafo KDE
|
||||
medía una imagen que nadie construye (daba 3 en deuda en vez de 4). Cada perfil se mide contra el
|
||||
árbol de recetas del que realmente sale. El mismo atajo estaba en `seed-graph.py --frontera`;
|
||||
corregido en los dos.
|
||||
- **La regla de la fuente única, implementada**: `deps_de()` usa la receta si existe y las aristas
|
||||
sembradas sólo si el nodo es `wanted`, marcando la procedencia (`[semilla]`) en la salida. Hoy no
|
||||
se ejerce (0 nodos `wanted`), pero queda escrita donde se va a usar.
|
||||
|
||||
**Enchufado al latido**: `cosecha-cron.sh` regenera `docs/state/drenaje.json` junto al grafo y lo
|
||||
commitea. La cola pasa a ser **derivada y versionada** — el `git diff` entre dos ciclos muestra qué
|
||||
salió de la deuda y qué onda se vació. **No lanza ningún build**: construir cuesta € y sigue siendo
|
||||
decisión explícita (`farm-up` + `campana-deuda.sh`), que es lo que el pie de la salida recuerda.
|
||||
|
||||
### P5 — jubilar las tandas (degradarlas, no borrarlas)
|
||||
|
||||
`tandas/` pasa a ser **archivo de campañas pasadas** (55 ficheros con historia, no se tiran). El
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
{
|
||||
"schema": "hammer-drenaje/1",
|
||||
"nota": "DERIVADO del grafo de estado por scripts/drenar.py — no editar a mano.",
|
||||
"perfiles": {
|
||||
"base": {
|
||||
"grafo": "docs/state/build-state.json",
|
||||
"nodos": 51,
|
||||
"en_deuda": 0,
|
||||
"ondas": 0,
|
||||
"profundidad": 0,
|
||||
"construibles_ya": [],
|
||||
"olas": []
|
||||
},
|
||||
"cli": {
|
||||
"grafo": "docs/state/build-state.json",
|
||||
"nodos": 74,
|
||||
"en_deuda": 0,
|
||||
"ondas": 0,
|
||||
"profundidad": 0,
|
||||
"construibles_ya": [],
|
||||
"olas": []
|
||||
},
|
||||
"escritorio-kde": {
|
||||
"grafo": "docs/state/build-state-kde.json",
|
||||
"nodos": 162,
|
||||
"en_deuda": 77,
|
||||
"ondas": 12,
|
||||
"profundidad": 12,
|
||||
"construibles_ya": [
|
||||
"qtbase"
|
||||
],
|
||||
"olas": [
|
||||
{
|
||||
"n": 1,
|
||||
"recetas": [
|
||||
"qtbase"
|
||||
]
|
||||
},
|
||||
{
|
||||
"n": 2,
|
||||
"recetas": [
|
||||
"qttools",
|
||||
"qtsvg",
|
||||
"qtshadertools",
|
||||
"qtlanguageserver",
|
||||
"qcoro"
|
||||
]
|
||||
},
|
||||
{
|
||||
"n": 3,
|
||||
"recetas": [
|
||||
"qtdeclarative",
|
||||
"kcodecs",
|
||||
"karchive",
|
||||
"kitemviews",
|
||||
"kglobalaccel",
|
||||
"kdbusaddons",
|
||||
"attica",
|
||||
"kdnssd",
|
||||
"kplotting"
|
||||
]
|
||||
},
|
||||
{
|
||||
"n": 4,
|
||||
"recetas": [
|
||||
"kcoreaddons",
|
||||
"kconfig",
|
||||
"ki18n",
|
||||
"kwidgetsaddons",
|
||||
"kguiaddons",
|
||||
"kwindowsystem",
|
||||
"solid",
|
||||
"sonnet",
|
||||
"kirigami",
|
||||
"kitemmodels",
|
||||
"qtwayland",
|
||||
"qt5compat",
|
||||
"qtmultimedia",
|
||||
"kholidays",
|
||||
"qtpositioning",
|
||||
"syntax-highlighting",
|
||||
"prison"
|
||||
]
|
||||
},
|
||||
{
|
||||
"n": 5,
|
||||
"recetas": [
|
||||
"kcolorscheme",
|
||||
"kcompletion",
|
||||
"kcrash",
|
||||
"kservice",
|
||||
"kbookmarks",
|
||||
"knotifications",
|
||||
"kpackage",
|
||||
"kauth",
|
||||
"plasma-activities",
|
||||
"kstatusnotifieritem",
|
||||
"kwayland",
|
||||
"kidletime",
|
||||
"krunner",
|
||||
"layer-shell-qt",
|
||||
"kdecoration",
|
||||
"qtspeech",
|
||||
"knighttime",
|
||||
"qtlocation"
|
||||
]
|
||||
},
|
||||
{
|
||||
"n": 6,
|
||||
"recetas": [
|
||||
"kconfigwidgets",
|
||||
"kiconthemes",
|
||||
"kjobwidgets",
|
||||
"kdeclarative",
|
||||
"kwallet",
|
||||
"ktextwidgets",
|
||||
"knewstuff",
|
||||
"kfilemetadata",
|
||||
"libkscreen",
|
||||
"plasma-activities-stats",
|
||||
"kded"
|
||||
]
|
||||
},
|
||||
{
|
||||
"n": 7,
|
||||
"recetas": [
|
||||
"kxmlgui",
|
||||
"ksvg"
|
||||
]
|
||||
},
|
||||
{
|
||||
"n": 8,
|
||||
"recetas": [
|
||||
"kio"
|
||||
]
|
||||
},
|
||||
{
|
||||
"n": 9,
|
||||
"recetas": [
|
||||
"kparts",
|
||||
"kcmutils",
|
||||
"libplasma",
|
||||
"kglobalacceld",
|
||||
"libksysguard"
|
||||
]
|
||||
},
|
||||
{
|
||||
"n": 10,
|
||||
"recetas": [
|
||||
"frameworkintegration",
|
||||
"ktexteditor",
|
||||
"kscreenlocker",
|
||||
"kwin"
|
||||
]
|
||||
},
|
||||
{
|
||||
"n": 11,
|
||||
"recetas": [
|
||||
"plasma-workspace",
|
||||
"qqc2-desktop-style",
|
||||
"breeze"
|
||||
]
|
||||
},
|
||||
{
|
||||
"n": 12,
|
||||
"recetas": [
|
||||
"plasma-integration"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"escritorio-mirada": {
|
||||
"grafo": "docs/state/build-state.json",
|
||||
"nodos": 31,
|
||||
"en_deuda": 4,
|
||||
"ondas": 2,
|
||||
"profundidad": 2,
|
||||
"construibles_ya": [
|
||||
"libinput",
|
||||
"mesa-swrast",
|
||||
"mirada-greeter"
|
||||
],
|
||||
"olas": [
|
||||
{
|
||||
"n": 1,
|
||||
"recetas": [
|
||||
"libinput",
|
||||
"mesa-swrast",
|
||||
"mirada-greeter"
|
||||
]
|
||||
},
|
||||
{
|
||||
"n": 2,
|
||||
"recetas": [
|
||||
"mirada-compositor"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+198
@@ -0,0 +1,198 @@
|
||||
#!/usr/bin/env python3
|
||||
# drenar.py — el ORDEN DE MASTICADO: qué construir, en qué orden, para acercar una imagen a estar
|
||||
# completa. (P4 del plan docs/plan-catalogo-objetivo.md.)
|
||||
#
|
||||
# POR QUÉ. La cola de la granja era AUTORÍA: 55 ficheros en `tandas/` escritos a mano, y para la
|
||||
# deuda una lista que el hub medía con `saldar-deuda-static.sh` y pegaba en `campana-deuda.sh`. Pero
|
||||
# el grafo de estado ya sabe qué falta (`state`), qué lo traba (`blocked_by`) y a cuántos destraba
|
||||
# (`unblocks`). Este script convierte eso en la cola: DERIVADA, no escrita.
|
||||
#
|
||||
# ONDAS. No lista "lo que falta" en un montón: lo parte en ondas topológicas. Onda 1 = construible
|
||||
# YA (ninguna dep en deuda). Onda 2 = lo que la onda 1 destraba. Etc. Eso da tres cosas que una
|
||||
# lista plana no da: el orden correcto, la PROFUNDIDAD real de la cadena (cuántos pasos secuenciales
|
||||
# faltan, que es lo que manda en el reloj de pared), y qué se puede paralelizar sin pisarse.
|
||||
# Dentro de cada onda ordena por `unblocks` desc: primero lo que libera más.
|
||||
#
|
||||
# LA REGLA DE LA FUENTE ÚNICA. Un nodo `wanted` (sin receta) no tiene deps propias; se le miran las
|
||||
# SEMBRADAS (`seed-edges.json`, P3). En cuanto tiene receta, manda la receta — ya corregida por el
|
||||
# veredicto de harkaq. La semilla es andamio y se cae sola.
|
||||
#
|
||||
# NO LANZA NADA. Sólo imprime/escribe la cola; construir cuesta € y es decisión explícita
|
||||
# (`farm-up` + `campana-deuda.sh`). Ver el pie de la salida.
|
||||
#
|
||||
# Uso: scripts/drenar.py --perfil escritorio-kde # ondas de una imagen
|
||||
# scripts/drenar.py # todo el corpus en deuda
|
||||
# scripts/drenar.py --perfil cli --n 20 # sólo las 20 primeras construibles ya
|
||||
# scripts/drenar.py --perfil escritorio-kde --tanda tandas/kde-drenaje.txt
|
||||
import json, sys
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
GRAFOS = ["docs/state/build-state.json", "docs/state/build-state-kde.json"]
|
||||
SEMILLA = ROOT / "docs/state/seed-edges.json"
|
||||
EN_DEUDA = ("debt", "never", "unhashable", "wanted")
|
||||
|
||||
|
||||
def cargar(perfil):
|
||||
"""Elige el grafo por la COLA que el perfil declara en `targets.toml`, no por cuál tiene más
|
||||
nodos. La diferencia importa: la cola `incoming-kde` SOMBREA recetas canónicas, así que medir
|
||||
`escritorio-mirada` contra el grafo KDE mide una imagen que nadie construye. Cada perfil se mide
|
||||
contra el árbol de recetas del que realmente sale."""
|
||||
sys.path.insert(0, str(ROOT / "scripts"))
|
||||
import targets as targets_mod
|
||||
cola = "corpus"
|
||||
if perfil is not None:
|
||||
try:
|
||||
cola = targets_mod.load().get(perfil, {}).get("cola", "corpus")
|
||||
except Exception:
|
||||
pass
|
||||
for f in GRAFOS:
|
||||
p = ROOT / f
|
||||
if not p.exists():
|
||||
continue
|
||||
d = json.loads(p.read_text())
|
||||
if cola not in (d.get("by_queue") or {}):
|
||||
continue
|
||||
if perfil is None or any(perfil in (r.get("perfiles") or []) for r in d["nodes"].values()):
|
||||
return d, f
|
||||
return None, None
|
||||
|
||||
|
||||
def deps_de(nombre, nodo, semilla):
|
||||
"""La regla de la fuente única: con receta manda la receta; sin receta (nodo `wanted`) se usan
|
||||
las aristas SEMBRADAS, que son hipótesis y se marcan como tales."""
|
||||
if nodo["state"] != "wanted":
|
||||
return nodo.get("deps") or [], "receta"
|
||||
aristas = (semilla.get("nodos") or {}).get(nombre)
|
||||
if isinstance(aristas, list):
|
||||
return [a["dep"] for a in aristas if a.get("clase") in ("conocida", "frontera")], "semilla"
|
||||
return [], "sin-datos"
|
||||
|
||||
|
||||
def ondas_de(perfil):
|
||||
"""(ondas, deuda, ambito, grafo) — el cálculo puro, sin imprimir. Reutilizado por --todos."""
|
||||
doc, cual = cargar(perfil)
|
||||
if doc is None:
|
||||
return None, None, None, None
|
||||
semilla = json.loads(SEMILLA.read_text()) if SEMILLA.exists() else {}
|
||||
nodos = doc["nodes"]
|
||||
ambito = {n: r for n, r in nodos.items()
|
||||
if perfil is None or perfil in (r.get("perfiles") or [])}
|
||||
deuda = {n: r for n, r in ambito.items() if r["state"] in EN_DEUDA}
|
||||
restante, ondas = dict(deuda), []
|
||||
while restante:
|
||||
onda = [n for n, r in restante.items()
|
||||
if not any(d in restante for d in deps_de(n, r, semilla)[0])]
|
||||
if not onda:
|
||||
ondas.append(sorted(restante)); break
|
||||
onda.sort(key=lambda n: (-restante[n].get("unblocks", 0), restante[n].get("cls", ""), n))
|
||||
ondas.append(onda)
|
||||
for n in onda:
|
||||
del restante[n]
|
||||
return ondas, deuda, ambito, cual
|
||||
|
||||
|
||||
def todos_los_perfiles():
|
||||
"""Artefacto versionado con el drenaje de CADA imagen. El `git diff` entre dos corridas es el
|
||||
avance real: qué salió de la deuda y qué onda se vació. Lo regenera el latido de la granja."""
|
||||
sys.path.insert(0, str(ROOT / "scripts"))
|
||||
import targets as targets_mod
|
||||
doc = {"schema": "hammer-drenaje/1",
|
||||
"nota": "DERIVADO del grafo de estado por scripts/drenar.py — no editar a mano.",
|
||||
"perfiles": {}}
|
||||
for p in sorted(targets_mod.load()):
|
||||
ondas, deuda, ambito, cual = ondas_de(p)
|
||||
if ondas is None:
|
||||
continue
|
||||
doc["perfiles"][p] = dict(
|
||||
grafo=cual, nodos=len(ambito), en_deuda=len(deuda), ondas=len(ondas),
|
||||
profundidad=len(ondas), construibles_ya=ondas[0] if ondas else [],
|
||||
olas=[{"n": i + 1, "recetas": o} for i, o in enumerate(ondas)])
|
||||
print(f" {p:20} {len(deuda):3} en deuda · {len(ondas):2} ondas · "
|
||||
f"onda 1 = {len(ondas[0]) if ondas else 0}")
|
||||
salida = ROOT / "docs/state/drenaje.json"
|
||||
salida.write_text(json.dumps(doc, indent=1, ensure_ascii=False) + "\n")
|
||||
print(f" → {salida.relative_to(ROOT)}")
|
||||
return 0
|
||||
|
||||
|
||||
def main():
|
||||
args = sys.argv[1:]
|
||||
if "--todos" in args:
|
||||
return todos_los_perfiles()
|
||||
perfil = args[args.index("--perfil") + 1] if "--perfil" in args else None
|
||||
tope = int(args[args.index("--n") + 1]) if "--n" in args else None
|
||||
tanda = args[args.index("--tanda") + 1] if "--tanda" in args else None
|
||||
|
||||
doc, cual = cargar(perfil)
|
||||
if doc is None:
|
||||
print(f"no encontré un grafo con el perfil `{perfil}` "
|
||||
f"(¿corriste scripts/build-state.py [--kde]?)", file=sys.stderr)
|
||||
return 1
|
||||
semilla = json.loads(SEMILLA.read_text()) if SEMILLA.exists() else {}
|
||||
nodos = doc["nodes"]
|
||||
|
||||
ambito = {n: r for n, r in nodos.items()
|
||||
if perfil is None or perfil in (r.get("perfiles") or [])}
|
||||
deuda = {n: r for n, r in ambito.items() if r["state"] in EN_DEUDA}
|
||||
etiqueta = f"perfil `{perfil}`" if perfil else "corpus entero"
|
||||
print(f"== drenaje de {etiqueta} ({cual})")
|
||||
print(f" {len(ambito)} nodos en ámbito · {len(deuda)} en deuda "
|
||||
f"({len(ambito)-len(deuda)} ya sellados)")
|
||||
if not deuda:
|
||||
print(" nada que drenar: la imagen está completa.")
|
||||
return 0
|
||||
|
||||
# Ondas topológicas sobre el subgrafo EN DEUDA: una receta sólo espera por deps que también
|
||||
# estén en deuda (las selladas ya están en el store y no bloquean).
|
||||
restante = dict(deuda)
|
||||
ondas, procedencia = [], {}
|
||||
while restante:
|
||||
onda = []
|
||||
for n, r in restante.items():
|
||||
ds, origen = deps_de(n, r, semilla)
|
||||
procedencia[n] = origen
|
||||
if not any(d in restante for d in ds):
|
||||
onda.append(n)
|
||||
if not onda: # ciclo entre nodos en deuda: no debería pasar (topo_ok), pero no se oculta
|
||||
print(f"\n ⚠ CICLO entre {len(restante)} nodos en deuda: "
|
||||
f"{' '.join(sorted(restante)[:10])}")
|
||||
ondas.append(sorted(restante))
|
||||
break
|
||||
onda.sort(key=lambda n: (-restante[n].get("unblocks", 0), restante[n].get("cls", ""), n))
|
||||
ondas.append(onda)
|
||||
for n in onda:
|
||||
del restante[n]
|
||||
|
||||
print(f"\n== {len(ondas)} ondas (profundidad real de la cadena; el reloj de pared manda acá)")
|
||||
for i, onda in enumerate(ondas, 1):
|
||||
muestra = onda if tope is None or i > 1 else onda[:tope]
|
||||
cab = f" onda {i}: {len(onda):3} construibles"
|
||||
if i == 1:
|
||||
cab += " ← ARRANCÁ POR ACÁ (nada las bloquea)"
|
||||
print(cab)
|
||||
for n in muestra[:25]:
|
||||
r = deuda[n]
|
||||
u = r.get("unblocks", 0)
|
||||
marca = f" [{procedencia[n]}]" if procedencia[n] != "receta" else ""
|
||||
print(f" {n:28} {r['state']:10} destraba {u:3} {r.get('cls','')}{marca}")
|
||||
if len(muestra) > 25:
|
||||
print(f" … y {len(muestra)-25} más")
|
||||
|
||||
primera = ondas[0][:tope] if tope else ondas[0]
|
||||
print(f"\n== cola para el worker ({len(primera)} recetas de la onda 1)")
|
||||
print(" " + " ".join(primera))
|
||||
if tanda:
|
||||
p = ROOT / tanda
|
||||
p.parent.mkdir(parents=True, exist_ok=True)
|
||||
p.write_text(f"# DERIVADA de {cual} por scripts/drenar.py — no editar a mano.\n"
|
||||
f"# {etiqueta}: onda 1 de {len(ondas)}, ordenada por `unblocks` desc.\n"
|
||||
+ "\n".join(primera) + "\n")
|
||||
print(f" → {tanda}")
|
||||
print("\n construir NO es gratis (€): esto no lanza nada. En el worker:")
|
||||
print(f" scripts/farm/campana-deuda.sh {' '.join(primera[:6])}…")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -82,6 +82,10 @@ echo "==> regenerando grafo de estado"
|
||||
if [ -x "$HAMMER" ]; then
|
||||
scripts/build-state.py >/dev/null 2>&1 && echo " build-state.json ✓" || echo " ⚠ build-state.py falló"
|
||||
scripts/build-state.py --kde >/dev/null 2>&1 && echo " build-state-kde.json ✓" || echo " ⚠ build-state.py --kde falló"
|
||||
# Cola DERIVADA del grafo (P4): el orden de masticado por imagen, en ondas topológicas. No lanza
|
||||
# ningún build — sólo deja escrito qué conviene moler primero, para que la cola deje de ser una
|
||||
# lista escrita a mano. Barato (lee JSON, no hashea).
|
||||
scripts/drenar.py --todos >/dev/null 2>&1 && echo " drenaje.json ✓" || echo " ⚠ drenar.py falló"
|
||||
else
|
||||
echo " ⚠ sin binario hammer en $HAMMER — no regenero estado"
|
||||
fi
|
||||
@@ -89,7 +93,7 @@ fi
|
||||
# 4. Commitear SÓLO el estado firme (el avance que el humano sigue). Recetas nuevas que YO autoré se
|
||||
# commitean aparte, a mano, para no meter autoría a medias en un commit de cron.
|
||||
if [ "${NO_COMMIT:-}" != "1" ]; then
|
||||
git add docs/state/build-state.json docs/state/build-state-kde.json 2>/dev/null || true
|
||||
git add docs/state/build-state.json docs/state/build-state-kde.json docs/state/drenaje.json 2>/dev/null || true
|
||||
if ! git diff --cached --quiet 2>/dev/null; then
|
||||
git commit -q -m "estado: cosecha granja $(ts) — avance del árbol KDE" 2>/dev/null \
|
||||
&& { git push -q origin main 2>/dev/null && echo "==> estado commiteado+pusheado" \
|
||||
|
||||
+11
-2
@@ -318,16 +318,25 @@ def frontera(recs, perfil):
|
||||
Cada candidato es una HIPÓTESIS a clasificar por un humano: puede ser (a) una dep opcional que
|
||||
hammer no habilitó a propósito, (b) un nix-ismo que falta en la lista de descarte, o (c) un
|
||||
hueco real. El sembrador no decide cuál: los ordena por cuántas recetas los piden."""
|
||||
# Los perfiles de una cola aparte (escritorio-kde) sólo existen en el grafo `--kde`.
|
||||
# El grafo se elige por la COLA que el perfil declara, no por cuál tiene más nodos: la cola
|
||||
# `incoming-kde` SOMBREA recetas canónicas, así que medir un perfil del corpus contra el grafo
|
||||
# KDE mide una imagen que nadie construye.
|
||||
try:
|
||||
cola = targets_mod.load().get(perfil, {}).get("cola", "corpus")
|
||||
except Exception:
|
||||
cola = "corpus"
|
||||
en_perfil, grafo_usado = [], None
|
||||
for f in ("docs/state/build-state.json", "docs/state/build-state-kde.json"):
|
||||
p = ROOT / f
|
||||
if not p.exists():
|
||||
continue
|
||||
d = json.loads(p.read_text())
|
||||
if cola not in (d.get("by_queue") or {}):
|
||||
continue
|
||||
hit = sorted(n for n, r in d["nodes"].items() if perfil in (r.get("perfiles") or []))
|
||||
if len(hit) > len(en_perfil):
|
||||
if hit:
|
||||
en_perfil, grafo_usado = hit, f
|
||||
break
|
||||
if not en_perfil:
|
||||
print(f"perfil `{perfil}` sin nodos en ningún grafo de estado "
|
||||
f"(¿regeneraste con scripts/build-state.py [--kde]?)", file=sys.stderr)
|
||||
|
||||
Reference in New Issue
Block a user