From 0a0471596ff46bae91c9c5f9b4d615e1315b8b45 Mon Sep 17 00:00:00 2001 From: wolverinaton Date: Mon, 31 Aug 2026 09:35:36 -0300 Subject: [PATCH] fix(gitnexus): reindexar no puede borrar los embeddings, y se verifica al final MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `npx gitnexus analyze` sin `--embeddings` no los deja como estaban: los BORRA. Hoy son 12.079 y regenerarlos es caro. El PostToolUse hook de Claude Code YA arma bien el comando —agrega el flag cuando detecta embeddings, `gitnexus-hook.cjs:284`— asi que no era ahi. El peligro esta en los docs del repo: AGENTS.md y CLAUDE.md muestran el comando pelado en dos lugares (linea 95 y el bloque "Keeping the Index Fresh"), y viven dentro de ``, que el propio analyze reescribe. Corregir ese texto dura hasta el proximo reindex: no se arregla documentando. Lo que si sobrevive a la regeneracion es una via que no depende de recordar el flag y que VERIFICA el invariante despues de correr: - `analyze_command(n)` decide el flag por el conteo real del indice. - `verify_preserved(before, after)` compara contra el conteo PREVIO, no contra cero: un indice que paso de 12.079 a 3 esta roto igual, y `after > 0` lo taparia. Sale distinto de cero si bajaron. - `embedding_count()` trata un meta ausente o ilegible como 0 sin reventar: un indice que todavia no existe no es un error. Recordar un flag es una esperanza; verificar el conteo despues es un hecho. 9 tests, y 4 mutaciones —no agregar nunca el flag, agregarlo siempre, detectar solo la perdida total, dar por bueno cualquier resultado— mueren cada una por su test propio. Verificado contra el indice real: reporta 12.079 y arma el comando con el flag. --- docs/generated/release-truth.json | 2 +- docs/generated/release-truth.md | 2 +- scripts/gitnexus_reindex.py | 96 ++++++++++++++++++++++++++++ tests/test_gitnexus_reindex_guard.py | 83 ++++++++++++++++++++++++ 4 files changed, 181 insertions(+), 2 deletions(-) create mode 100644 scripts/gitnexus_reindex.py create mode 100644 tests/test_gitnexus_reindex_guard.py diff --git a/docs/generated/release-truth.json b/docs/generated/release-truth.json index 84258f6..f64edbc 100644 --- a/docs/generated/release-truth.json +++ b/docs/generated/release-truth.json @@ -135,7 +135,7 @@ "console_entrypoints": 8, "mcp_tools": 51, "ops_cli_commands": 5, - "pytest_test_functions": 4140 + "pytest_test_functions": 4149 }, "feature_profile_matrix": { "capture_hook": [ diff --git a/docs/generated/release-truth.md b/docs/generated/release-truth.md index 822dcb7..80bead6 100644 --- a/docs/generated/release-truth.md +++ b/docs/generated/release-truth.md @@ -13,7 +13,7 @@ Do not edit this file by hand. Run `python scripts/generate_release_truth.py`. - Main CLI commands: **119** - Operations CLI commands: **5** - Console entrypoints: **8** -- Pytest source test functions: **4140** +- Pytest source test functions: **4149** ## MCP tools diff --git a/scripts/gitnexus_reindex.py b/scripts/gitnexus_reindex.py new file mode 100644 index 0000000..3d73353 --- /dev/null +++ b/scripts/gitnexus_reindex.py @@ -0,0 +1,96 @@ +"""Reindexa GitNexus sin perder los embeddings, y lo PRUEBA despues de correr. + +POR QUE EXISTE. `npx gitnexus analyze` sin `--embeddings` no deja los embeddings +como estaban: los BORRA. Hoy son 12.079 y regenerarlos es caro. El PostToolUse +hook ya arma bien el comando (agrega el flag cuando detecta embeddings), pero los +docs del repo — `AGENTS.md` y `CLAUDE.md`, dentro del bloque generado +`` — muestran el comando pelado en dos lugares. Ese bloque +lo reescribe el propio `analyze`, asi que corregirlo a mano dura hasta el proximo +reindex: no se puede arreglar editando el texto. + +Lo que si sobrevive es esto: una via segura que no depende de recordar el flag, y +un chequeo POSTERIOR que compara el conteo de embeddings antes y despues. Si +bajaron, sale distinto de cero y lo dice. Recordar un flag es una esperanza; +verificar el invariante es un hecho. + +Uso: + python scripts/gitnexus_reindex.py # reindexa preservando + python scripts/gitnexus_reindex.py --check # solo reporta, no corre nada +""" +from __future__ import annotations + +import json +import subprocess +import sys +from pathlib import Path + +REPO = Path(__file__).resolve().parents[1] +META = REPO / ".gitnexus" / "meta.json" + + +def embedding_count(meta_path: Path = META) -> int: + """Cuantos embeddings tiene el indice hoy. 0 si no hay indice.""" + if not meta_path.is_file(): + return 0 + try: + data = json.loads(meta_path.read_text(encoding="utf-8")) + except (json.JSONDecodeError, OSError): + return 0 + stats = data.get("stats") or {} + try: + return int(stats.get("embeddings") or 0) + except (TypeError, ValueError): + return 0 + + +def analyze_command(embeddings: int) -> list[str]: + """El comando correcto para el estado actual del indice. + + Con embeddings existentes, `--embeddings` no es opcional: sin el, analyze + los borra. Sin embeddings, agregarlo obligaria a generarlos, que es un + trabajo distinto del que se pidio. + """ + cmd = ["npx", "gitnexus", "analyze"] + if embeddings > 0: + cmd.append("--embeddings") + return cmd + + +def verify_preserved(before: int, after: int) -> tuple[bool, str]: + """El chequeo que hace util a este script: ¿sobrevivieron? + + No alcanza con `after > 0`: un indice que paso de 12.079 a 3 esta roto + igual, y "hay algunos" lo taparia. + """ + if before == 0: + return True, f"no habia embeddings que preservar (after={after})" + if after < before: + return False, ( + f"EMBEDDINGS PERDIDOS: {before} -> {after}. Analyze corrio sin" + " --embeddings o fallo a mitad. Regenerarlos es caro." + ) + return True, f"embeddings preservados: {before} -> {after}" + + +def main(argv: list[str]) -> int: + before = embedding_count() + cmd = analyze_command(before) + print(f"embeddings antes: {before}") + print(f"comando: {' '.join(cmd)}") + + if "--check" in argv[1:]: + return 0 + + result = subprocess.run(cmd, cwd=REPO, shell=(sys.platform == "win32")) + if result.returncode != 0: + print(f"analyze fallo con codigo {result.returncode}") + return result.returncode + + after = embedding_count() + ok, mensaje = verify_preserved(before, after) + print(mensaje) + return 0 if ok else 1 + + +if __name__ == "__main__": + sys.exit(main(sys.argv)) diff --git a/tests/test_gitnexus_reindex_guard.py b/tests/test_gitnexus_reindex_guard.py new file mode 100644 index 0000000..6454a5a --- /dev/null +++ b/tests/test_gitnexus_reindex_guard.py @@ -0,0 +1,83 @@ +"""El reindex de GitNexus no puede borrar los embeddings, y lo prueba al final. + +`npx gitnexus analyze` sin `--embeddings` los BORRA en vez de dejarlos. Son +12.079 y regenerarlos es caro. El hook de Claude Code ya arma bien el comando, +pero los docs del repo muestran el comando pelado en dos lugares — y viven +dentro del bloque ``, que el propio analyze reescribe, asi +que corregir el texto no dura hasta el proximo reindex. + +Por eso el arreglo no es documental: es una via segura que arma el comando sola +y VERIFICA el conteo despues de correr. Estos tests fijan las dos mitades — que +el flag se agregue cuando corresponde, y que la verificacion posterior detecte la +perdida en vez de dar por buena cualquier corrida que termine en 0. +""" +from __future__ import annotations + +import json +from pathlib import Path + +from scripts.gitnexus_reindex import ( + analyze_command, + embedding_count, + verify_preserved, +) + + +# --- el comando ------------------------------------------------------------- + +def test_con_embeddings_el_flag_no_es_opcional(): + assert analyze_command(12079) == ["npx", "gitnexus", "analyze", "--embeddings"] + + +def test_sin_embeddings_no_se_agrega_el_flag(): + """Agregarlo obligaria a generarlos, que es otro trabajo del que se pidio.""" + assert analyze_command(0) == ["npx", "gitnexus", "analyze"] + + +# --- la verificacion posterior, que es lo que hace util al script ----------- + +def test_detecta_la_perdida_total(): + ok, msg = verify_preserved(12079, 0) + assert not ok and "PERDIDOS" in msg + + +def test_detecta_la_perdida_PARCIAL(): + """Un indice que paso de 12.079 a 3 esta roto igual. + + Este es el caso que un chequeo ingenuo (`after > 0`) daria por bueno, y por + eso la comparacion es contra el conteo previo y no contra cero. + """ + ok, msg = verify_preserved(12079, 3) + assert not ok, msg + + +def test_acepta_que_crezcan(): + ok, _ = verify_preserved(12074, 12079) + assert ok + + +def test_acepta_que_no_hubiera_nada_que_preservar(): + ok, _ = verify_preserved(0, 0) + assert ok + + +# --- lectura del meta ------------------------------------------------------- + +def test_lee_el_conteo_del_meta(tmp_path: Path): + meta = tmp_path / "meta.json" + meta.write_text(json.dumps({"stats": {"embeddings": 12079}}), encoding="utf-8") + assert embedding_count(meta) == 12079 + + +def test_meta_ausente_o_ilegible_cuenta_cero_sin_reventar(tmp_path: Path): + """Un indice que no existe todavia no es un error: es cero.""" + assert embedding_count(tmp_path / "no-existe.json") == 0 + roto = tmp_path / "roto.json" + roto.write_text("{no es json", encoding="utf-8") + assert embedding_count(roto) == 0 + + +def test_meta_sin_el_campo_cuenta_cero(tmp_path: Path): + meta = tmp_path / "meta.json" + meta.write_text(json.dumps({"stats": {}}), encoding="utf-8") + assert embedding_count(meta) == 0