Files
sergioandClaude Opus 4.8 0b333b4c9e gnome: integra el frente a yupana — perfil escritorio-gnome + reckoning honesto
1. targets.toml: [perfil.escritorio-gnome] (9 raíces de sesión, cola incoming-gnome)
   ⇒ `yupana objetivo` ya lo ve; fluye por targets.py. build-state lo SALTA hasta
   que la cola se cargue (--gnome), así declararlo no reporta raíces fantasma.
2. seed-gnome.py OPTIMIZADO: hornea la triage (sustitución/tooling/opcional/espinazo)
   como HIPÓTESIS con el caveat "el cierre de nixpkgs sobreestima ~5-10×", marca el
   keystone (spidermonkey→gjs→gnome-shell), y deja de mentir con el 465 crudo. El
   espinazo (353) se reporta como COTA ALTA, no como lista de build.

Falta (se activa al autorar): grafo build-state-gnome + flag --gnome ⇒ keystones/
drenar nativos sobre GNOME. Hoy la capa OBJETIVO del yupana-gnome está; la GRAFO no.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 15:58:23 -04:00

155 lines
9.2 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
# seed-gnome.py — siembra el SUBTREE transitivo de GNOME desde metadata de nixpkgs (NO construye con
# nix, ADR 0004: sólo lee deps). Corre `sembrar_cierre` de seed-graph sobre las raíces de una sesión
# GNOME y clasifica el cierre:
# conocida → ya hay receta en el corpus (cache-hit potencial)
# frontera → nixpkgs lo pide y hammer NO tiene receta → HAY QUE AUTORAR (esto es el trabajo real)
# nix-ismo/lab → ruido de nixpkgs o lo provee el sandbox (se descarta)
#
# Cada `frontera` es una HIPÓTESIS a clasificar/autorar por un humano (o un subagente-por-receta), NO
# se promueve a receta automáticamente. Salida durable a docs/state/seed-gnome.json.
import json, sys, pathlib, importlib.util
# el guion en `seed-graph.py` impide `import seed_graph`: lo cargo por ruta.
_spec = importlib.util.spec_from_file_location(
"seed_graph", str(pathlib.Path(__file__).parent / "seed-graph.py"))
sg = importlib.util.module_from_spec(_spec); _spec.loader.exec_module(sg)
ROOT = pathlib.Path(__file__).resolve().parent.parent
OUT = ROOT / "docs/state/seed-gnome.json"
# Raíces de una sesión GNOME usable: el compositor (mutter), el shell y su runtime JS (gjs), la
# sesión y sus daemons, el display manager, el portal, y las apps mínimas (files, terminal, ajustes).
# El cierre transitivo trae el resto (mozjs bajo gjs, gcr/libsecret bajo el keyring, etc.).
RAICES = [
"mutter", "gnome-shell", "gjs", "gnome-session", "gnome-settings-daemon",
"gsettings-desktop-schemas", "gnome-desktop", "gnome-menus", "gnome-keyring",
"gdm", "xdg-desktop-portal", "xdg-desktop-portal-gnome", "libadwaita",
"nautilus", "gnome-console", "gnome-control-center", "gnome-text-editor",
]
MINIMA = [ # lo que una sesión GNOME necesita para BOOTEAR a un shell usable (sin apps)
"mutter", "gnome-shell", "gjs", "gnome-session", "gnome-settings-daemon",
"gsettings-desktop-schemas", "gnome-desktop", "gdm", "xdg-desktop-portal-gnome",
]
# ── LA OPTIMIZACIÓN (el "yupana de gnome", no un sembrador crudo). El cierre de metadata de nixpkgs
# SOBREESTIMA ~5-10× la frontera real de hammer, porque declara buildInputs de features MÁXIMAS. Estas
# tablas son HIPÓTESIS de triage (NO verdad — la verdad emerge autorando feature-minimal), para que el
# reporte no mienta con "465 recetas". Cada una vuelve legible por qué un candidato probablemente NO es
# hueco. Clasificar a mano al autorar; mover entre cubetas es esperable.
SUSTITUCION = { # hammer YA tiene equivalente → mapeo, no hueco
"systemd", "systemd-minimal", "systemd-minimal-libs", # → elogind
"libpulseaudio", # → pipewire (libpulse compat)
"networkmanager", # → iwd / systemd-networkd (a elegir)
}
TOOLING = { # generadores de docs / build-glue: el lab los provee o se deshabilitan
"vala", "gi-docgen", "gtk-doc", "docutils", "doxygen", "texinfo", "autoconf", "automake",
"autoconf-archive", "xmlto", "nasm", "yasm", "cmocka", "gtest", "bash-completion",
"desktop-file-utils", "gettext", "itstool", "sassc", "meson", "ninja", "help2man", "gperf",
"appstream", "appstream-glib", "asciidoc", "docbook-xsl", "docbook-xml", "docbook-sgml",
"docbook5", "perl", "python3", "gobject-introspection", "sphinx", "graphviz", "groff", "swig",
"dejagnu", "expect", "valgrind", "gdb", "man-db", "po4a", "ragel", "byacc", "gengetopt",
"validate-pkg-config", "prune-libtool-files",
}
# subárboles de features/apps que una sesión mínima NO necesita (defer): media, impresión, red,
# almacenamiento, TPM, escáner, runtimes de scripting arrastrados por deps opcionales.
OPCIONAL_PREF = (
"ffmpeg", "gst-plugins-bad", "gst-plugins-ugly", "gst-libav", "x264", "x265", "svt-av1",
"dav1d", "libaom", "libvpx", "fdk-aac", "libmad", "libmpg123", "mpg123", "opusfile", "wavpack",
"libtheora", "vorbis", "zimg", "vid.stab", "libass", "srt", "zvbi", "sbc", "ldacbt", "liblc3",
"samba", "talloc", "tevent", "tdb", "ldb", "libnfs", "libiscsi", "cyrus-sasl", "net-snmp",
"unbound", "cups", "sane", "colord", "argyllcms", "bluez", "freerdp", "evolution",
"gnome-online-accounts", "gnome-remote-desktop", "webkitgtk", "gnome-clocks", "flatpak",
"ostree", "gvfs", "openjdk", "ruby", "lua", "tcl", "tpm2", "swtpm", "libtpms", "cryptsetup",
"lvm2", "libnvme", "libndctl", "mariadb", "psqlodbc", "unixodbc", "openldap", "krb5",
"sphinxcontrib", "geoclue", "libcamera", "roc-toolkit", "exiv2", "poppler",
)
def _cubeta(dep):
if dep in SUSTITUCION: return "sustitucion"
if dep in TOOLING: return "tooling"
if any(t in dep for t in OPCIONAL_PREF): return "opcional"
return "espinazo"
def main():
recs = sg.recetas()
raices = MINIMA if (len(sys.argv) > 1 and sys.argv[1] == "--minima") else RAICES
print(f"== raíces GNOME: {len(raices)}", file=sys.stderr)
grafo = sg.sembrar_cierre(raices, recs, prof_max=6, tope=4000)
out_path = ROOT / ("docs/state/seed-gnome-minima.json" if raices is MINIMA else "docs/state/seed-gnome.json")
# clasificar TODO el cierre
clases = {"conocida": [], "frontera": [], "nix-ismo": [], "lab": [], "sin-attr": []}
for n, aristas in grafo.items():
if aristas is None:
clases["sin-attr"].append(n); continue
# nodos del cierre = todo lo referido como dep, clasificado por su presencia
todos_deps = {}
for n, aristas in grafo.items():
for a in (aristas or []):
d = a["dep"]
# el nodo se clasifica por su MEJOR evidencia (conocida > frontera)
prev = todos_deps.get(d)
rank = {"conocida": 3, "lab": 2, "nix-ismo": 1, "frontera": 0}
if prev is None or rank[a["clase"]] > rank[prev["clase"]]:
todos_deps[d] = a
conocidas = sorted(d for d, a in todos_deps.items() if a["clase"] == "conocida")
frontera = sorted(d for d, a in todos_deps.items() if a["clase"] == "frontera")
nixismos = sorted(d for d, a in todos_deps.items() if a["clase"] == "nix-ismo")
lab = sorted(d for d, a in todos_deps.items() if a["clase"] == "lab")
sin_attr = sorted(clases["sin-attr"])
# ¿cuántas veces piden cada frontera? (prioridad de autoría)
demanda = {}
for n, aristas in grafo.items():
for a in (aristas or []):
if a["clase"] == "frontera":
demanda.setdefault(a["dep"], []).append(n)
print(f"\n== SUBTREE GNOME (cierre transitivo, {len(grafo)} nodos visitados)")
print(f" ya tenemos receta (conocida): {len(conocidas)}")
print(f" FRONTERA a autorar: {len(frontera)}")
print(f" nix-ismos (ruido): {len(nixismos)}")
print(f" provisto por el lab: {len(lab)}")
print(f" sin attr en nixpkgs: {len(sin_attr)}"
+ (f" ({' '.join(sin_attr[:8])}…)" if sin_attr else ""))
# ── TRIAGE (hipótesis): parte la frontera cruda en cubetas para no mentir con el conteo bruto.
cubetas = {"sustitucion": [], "tooling": [], "opcional": [], "espinazo": []}
for d in frontera:
cubetas[_cubeta(d)].append(d)
espinazo = sorted(cubetas["espinazo"], key=lambda k: (-len(demanda.get(k, [])), k))
# keystone / palo largo: gjs bloquea gnome-shell, y gjs cuelga de spidermonkey(mozjs).
palo_largo = [d for d in ("spidermonkey", "gjs", "mozjs") if d in frontera]
print(f"\n== TRIAGE de la frontera cruda (HIPÓTESIS — el cierre de nixpkgs sobreestima ~5-10×):")
print(f" sustitución (systemd→elogind…): {len(cubetas['sustitucion'])}")
print(f" tooling/docs (lab o disable): {len(cubetas['tooling'])}")
print(f" opcional (media/print/red/…): {len(cubetas['opcional'])}")
print(f" ⇒ ESPINAZO candidato a autorar: {len(espinazo)} (SIGUE siendo cota ALTA)")
print(f" ⚠ palo largo / keystone: {' → '.join(palo_largo) or '—'} (bloquea gnome-shell)")
print(f"\n== espinazo por demanda — top 30 (hipótesis, clasificar al autorar):")
for d in espinazo[:30]:
print(f" {len(demanda.get(d, [])):3}× {d}")
doc = dict(
schema="hammer-seed-gnome/2",
fuente="nixpkgs metadata (NO se construye con nix — ADR 0004)",
advertencia=("El cierre de metadata SOBREESTIMA ~5-10× la frontera real de hammer (buildInputs "
"de features máximas). Las cubetas son HIPÓTESIS, no verdad: la frontera real "
"emerge autorando TOP-DOWN feature-minimal. NO promover a receta automáticamente."),
raices=raices,
resumen=dict(visitados=len(grafo), conocida=len(conocidas), frontera_cruda=len(frontera),
nix_ismo=len(nixismos), lab=len(lab), sin_attr=len(sin_attr),
sustitucion=len(cubetas["sustitucion"]), tooling=len(cubetas["tooling"]),
opcional=len(cubetas["opcional"]), espinazo=len(espinazo)),
palo_largo=palo_largo,
espinazo=espinazo, triage=cubetas,
conocida=conocidas, frontera=frontera, nix_ismo=nixismos, lab=lab, sin_attr=sin_attr,
demanda={d: sorted(set(q)) for d, q in sorted(demanda.items(), key=lambda kv:(-len(kv[1]),kv[0]))},
)
out_path.write_text(json.dumps(doc, indent=1, ensure_ascii=False) + "\n")
print(f"\n{out_path.relative_to(ROOT)}")
return 0
if __name__ == "__main__":
sys.exit(main())