Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/generated/release-truth.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": [
Expand Down
2 changes: 1 addition & 1 deletion docs/generated/release-truth.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
96 changes: 96 additions & 0 deletions scripts/gitnexus_reindex.py
Original file line number Diff line number Diff line change
@@ -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
`<!-- gitnexus:start -->` — 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))
83 changes: 83 additions & 0 deletions tests/test_gitnexus_reindex_guard.py
Original file line number Diff line number Diff line change
@@ -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 `<!-- gitnexus:start -->`, 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
Loading