# justfile — superficie de tareas de HECATE
#
# Un solo punto de entrada para todo lo que se hace a mano en este repositorio:
# verificar, medir, empaquetar. `just check` es el equivalente de la verificación que
# exige el hook `pre-push`, ejecutable en cualquier máquina sin CI en la nube.
#
# Por qué just y no Make: las recetas de Make exigen tabuladores, la expansión de
# variables es textual (una errata se convierte en otra orden distinta) y en Windows
# obliga a tener `make`. just valida la sintaxis al arrancar, lista las recetas con su
# descripción y funciona igual en PowerShell y en sh.
#
# Uso:
#   just                # = just check
#   just --list         # recetas disponibles
#   just doctor         # qué herramientas hay en esta máquina

set shell := ["sh", "-uc"]
set windows-shell := ["powershell.exe", "-NoProfile", "-Command"]

# Intérprete de Python del entorno virtual del repositorio.
# `HECATE_PYTHON` permite apuntar a un entorno FUERA del árbol de trabajo: si el
# venv vive dentro de OneDrive, el motor de sincronización recorre ~19.000
# ficheros de `site-packages` y acaba corrompiendo el árbol. Sin la variable, se
# usa `.venv` como siempre.
py := env_var_or_default("HECATE_PYTHON", if os() == "windows" { ".venv\\Scripts\\python.exe" } else { ".venv/bin/python" })

# bash para los scripts .sh. En unix está en el PATH. En Windows lo instala Git for
# Windows en su propio árbol y NO lo expone, así que se usa su ruta canónica. No se
# resuelve buscando `bash` en el PATH a propósito: con WSL instalado, `bash` apunta a
# un intérprete Linux que no puede ejecutar rutas de Windows y el fallo sería
# desconcertante. Si Git está en otra ruta, `just doctor` lo dice.
bash_bin := if os() == "windows" { "C:\\Program Files\\Git\\bin\\bash.exe" } else { "bash" }

# Atajo de `just check`: lo que se ejecuta antes de cada commit.
default: check

# --- verificación -----------------------------------------------------------

# Verificación completa de código: lint + test + laboratorio.
check: lint test lab

# Comprueba el laboratorio Docker sin ejecutarlo: sintaxis de los scripts, estructura del
# compose y coherencia con el `.dockerignore`. No levanta contenedores y por eso puede
# formar parte de `check`: la máquina de desarrollo no tiene Docker.
lab:
    {{py}} scripts/check_docker_lab.py

# Lint de los dos planos. Falla ante cualquier aviso.
lint:
    cargo fmt --all -- --check
    cargo clippy --workspace --all-targets -- -D warnings
    {{py}} scripts/audit-allows.py
    {{py}} -m ruff check --no-fix .
    {{py}} -m ruff format --check .
    {{py}} -m mypy
    @echo "OK: lint"

# Ningún `#[allow]` sin justificar ni redundante (hallazgo R2 del audit del 2026-10-23).
audit-allows:
    {{py}} scripts/audit-allows.py

# `clippy::pedantic` con la lista de excepciones justificadas de Cargo.toml aplicada.
# Fuera de esa lista no puede quedar ni un aviso; si aparece, o se corrige o se documenta
# en Cargo.toml con su razón.
pedantic:
    cargo clippy --workspace --all-targets -- -D warnings -W clippy::pedantic -A clippy::module_name_repetitions -A clippy::missing_errors_doc -A clippy::missing_panics_doc -A clippy::must_use_candidate -A clippy::cast_precision_loss -A clippy::cast_possible_truncation -A clippy::cast_sign_loss -A clippy::doc_markdown -A clippy::too_many_lines -A clippy::items_after_statements
    @echo "OK: pedantic sin avisos no justificados"

# Fuzz dirigido contra el framing y el bucle de conexión (60 s por defecto).
# No va en `check` porque tarda; se lanza a mano antes de una release.
fuzz seconds="60":
    HECATE_FUZZ_SECONDS={{seconds}} cargo test -p mesh-worker-net --test fuzz_robustness -- --nocapture

# Tests de los dos planos.
test:
    cargo test --workspace
    {{py}} -m pytest -q
    @echo "OK: test"

# Auditoría completa del repositorio: versión, andamiaje y verify.sh.
audit:
    {{py}} scripts/check_versions.py
    {{py}} scripts/audit-allows.py
    & "{{bash_bin}}" scripts/check-scaffolding.sh
    & "{{bash_bin}}" scripts/verify.sh

# Inventario de deuda residual (10 detecciones, ninguna corrección). No es una compuerta.
audit-residual:
    & "{{bash_bin}}" scripts/audit-residual.sh
# Verificación íntegra con scripts/verify.sh (el mismo que el hook pre-push).
verify:
    & "{{bash_bin}}" scripts/verify.sh

# Comprueba que Cargo.toml, CHANGELOG.md y CITATION.cff declaran la misma versión.
versions:
    {{py}} scripts/check_versions.py

# Crea la etiqueta de release, o se niega y explica por qué.
# Exige árbol limpio, HEAD == origin/main, CHANGELOG documentado y la etiqueta sin colisiones.
# En la v2.0.0 la etiqueta acabó en un commit que no era el de release por ponerla sobre HEAD
# sin comprobarlo; esto lo hace imposible. `just release-check` sólo verifica.
release-tag *args:
    & "{{bash_bin}}" scripts/tag-release.sh {{args}}

release-check:
    & "{{bash_bin}}" scripts/tag-release.sh --check

# Recuenta los números del proyecto y escribe examples/metrics.json.
# Los cuenta con las herramientas que los producen; no los lee de ninguna parte.
metrics:
    & "{{bash_bin}}" scripts/count-tests.sh

# --- construcción y empaquetado ---------------------------------------------

# Build de release del plano de control.
build:
    cargo build --release --workspace
    @echo "OK: target/release/hecate-node"

# Rueda del plano de datos. El paquete es Python puro (hatchling, sin PyO3), así que
# no se usa maturin: `maturin build` solo aplica a extensiones de Rust expuestas a
# Python, y aquí no hay ninguna. La versión se lee de Cargo.toml.
wheel:
    {{py}} -m pip wheel . --no-deps --wheel-dir dist
    @echo "OK: dist/"

# --- medición ---------------------------------------------------------------

# El binario de release es obligatorio: en debug el benchmark de cómputo da ~70x
# menos GFLOPS y el tier del nodo sale mal clasificado. `bench` y `profile` se niegan
# a ejecutarse en debug salvo que se pase --allow-debug.

# Perfila esta máquina y guarda el perfil con procedencia por campo.
profile:
    cargo build --release -p hecate_node
    cargo run --release -p hecate_node -- profile --out docs/benchmarks/hardware-local.json

# Mide la garantía de convivencia con el host (CPU/RAM y latencia bajo carga).
guarantee seconds="25":
    cargo build --release -p hecate_node
    pwsh -NoProfile -File scripts/measure-host-guarantee.ps1 -Seconds {{seconds}} -Out docs/benchmarks/host-guarantee.json

# Benchmark del pipeline contra el plano de datos simulado.
bench requests="400":
    cargo build --release -p hecate_node
    cargo run --release -p hecate_node -- bench --mock --nodes 3 --requests {{requests}} --concurrency 1,8,32

# Benchmark del pipeline contra un MOTOR REAL (HTTP), no contra el plano de datos.
# Es otra ruta: `bench` va por gRPC, esto va por `InferenceBackend`. No se mezclan.
# Requiere un motor escuchando; por defecto `llama-server` en 127.0.0.1:8080.
bench-engine backend="llama_cpp" url="http://127.0.0.1:8080" tokens="16" requests="24":
    cargo build --release -p hecate_node
    cargo run --release -p hecate_node -- bench-engine --backend {{backend}} --backend-url {{url}} --nodes 1 --tokens {{tokens}} --requests {{requests}} --concurrency 1,8,32

# Tests de integración contra un MOTOR REAL. No van en `check`: exigen un binario y un
# modelo cargado, y `check` tiene que pasar en una máquina sin motor.
#   llama-server -m models/qwen2.5-0.5b-instruct-q4_k_m.gguf --port 8080 --no-webui
test-integration url="http://127.0.0.1:8080":
    @echo "requiere un motor escuchando en {{url}} (ver docs/benchmarks/llama-cpp-setup.md)"
    & "{{bash_bin}}" -c 'HECATE_LLAMA_CPP_URL={{url}} cargo test -p mesh-integration-tests --test backend_integration -- --ignored --nocapture'

# --- mantenimiento ----------------------------------------------------------

# Regenera los fixtures de paridad (cuantización y aceleración) y comprueba que quedaron al día.
fixture:
    {{py}} scripts/emit_quant_fixture.py
    {{py}} scripts/emit_quant_fixture.py --check
    {{py}} scripts/emit_acceleration_fixture.py
    {{py}} scripts/emit_acceleration_fixture.py --check

# Regenera los stubs de gRPC de Python a partir del .proto.
proto:
    & "{{bash_bin}}" scripts/gen-proto.sh

# Instala los hooks de git (pre-push → scripts/verify.sh).
hooks:
    & "{{bash_bin}}" scripts/install-hooks.sh

# Limpia artefactos de build. No toca código ni documentos.
clean:
    cargo clean
    @echo "OK: target/ limpio"

# Qué hay instalado en esta máquina y qué falta.
doctor:
    @echo "--- herramientas"
    cargo --version
    rustc --version
    just --version
    {{py}} --version
    git --version
    @echo "--- bash para los scripts .sh"
    & "{{bash_bin}}" --version
