Files
takana/scripts/seed-gnome.py
T
Sergio 476168bb07 takana etapa 5c: comentarios de scripts, MOTD, y un BUG que introdujo la etapa 4
250 líneas de comentario en 151 scripts. Control verificado: el diff no toca
NI UNA línea que no empiece por #, y la sintaxis de los 151 pasa.

El barrido saltea heredocs y cadenas triples, y el guardián DISPARÓ 3 veces:
las tres eran el MOTD que el script escribe DENTRO de la imagen construida —
texto del producto, no comentario del script. Se cambiaron aparte y a
propósito, que es rebranding, no limpieza.

Y el hallazgo caro:  casaba contra ,
que es el TARGET de tracing — o sea el module_path!, o sea el nombre del crate.
La etapa 4 lo movió a  y el script quedó casando NADA. No fallaba:
imprimía cero atribuciones, indistinguible de un log sin problemas. Comprobado
con el binario (RUST_LOG=info sobre zlib), no deducido. Ahora acepta las dos, y
tiene que seguir aceptándolas porque los logs viejos en disco dicen la vieja.

Además 14 rutas de módulo  en docs, que el barrido anterior no tocó
porque  no es frontera de palabra.
2026-09-09 19:28:48 +00:00

155 lines
9.2 KiB
Python
Raw 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 takana 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 takana, 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())