Ctrl K
ComunidadGuías…

Justfile

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
set shell := ["bash", "-euo", "pipefail", "-c"]
rust_dev_env := "if command -v mold >/dev/null 2>&1; then export RUSTFLAGS=\"${RUSTFLAGS:-} -C link-arg=-fuse-ld=mold\"; fi"
local_release_profile := "release-fast"

default:
    @just --list

# Safely inspect and tune the tootie TEI deployment.
tei-status:
    ./scripts/tei-tune.py status

tei-presets:
    ./scripts/tei-tune.py presets

tei-apply preset="rtx4070-axon" *args:
    ./scripts/tei-tune.py apply {{preset}} {{args}}

tei-benchmark *args:
    ./scripts/tei-tune.py benchmark {{args}}

tei-rollback:
    ./scripts/tei-tune.py rollback

# Bootstrap a new development environment (checks + installs all dependencies).
# No just? Run ./scripts/dev-setup.sh directly — it installs just for you.
setup *args:
    ./scripts/dev-setup.sh {{args}}

check:
    {{rust_dev_env}}; cargo check -q --locked

check-tests:
    {{rust_dev_env}}; cargo check -q --tests --locked

test:
    if cargo nextest --version >/dev/null 2>&1; then {{rust_dev_env}}; RUST_MIN_STACK=8388608 cargo nextest run --locked --workspace; else echo "cargo-nextest not installed; falling back to cargo test"; {{rust_dev_env}}; RUST_MIN_STACK=8388608 cargo test -q --locked; fi

test-fast:
    if cargo nextest --version >/dev/null 2>&1; then {{rust_dev_env}}; RUST_MIN_STACK=8388608 cargo nextest run --locked --lib; else {{rust_dev_env}}; RUST_MIN_STACK=8388608 cargo test -q --lib --locked; fi

# Deterministic, network-free drift and schema gate for the shared E2E catalog.
e2e-catalog-check:
    ./scripts/e2e/validate-catalog.py --report
    python3 -m unittest tests/e2e/catalog/test_validate_catalog.py

# Portable, network-free contracts for run allocation and provider doubles.
e2e-isolation-check:
    python3 tests/e2e/run_isolation_tests.py

# Execute an allocation plan through authoritative teardown and emit the sole
# canonical JSON + JUnit execution/evidence report.
e2e-supervised-report plan report="target/e2e/report.json" junit="target/e2e/junit.xml":
    python3 scripts/e2e/run-supervised-suite.py {{plan}} --report {{report}} --junit {{junit}}

# Supported schema-epoch upgrade contract: digest-pinned synthetic fixture,
# current tested binary, semantic persistence, negative cases, and teardown.
e2e-upgrade:
    python3 -m unittest discover -s tests/e2e/upgrade -p 'test_*.py'
    python3 scripts/e2e/run-upgrade.py --binary target/debug/axon

# Early, non-required measured E2E slice. Provider doubles and loopback-only
# routing are mandatory; the runner always emits a cleanup/budget report.
e2e-hermetic:
    ./scripts/e2e/run-hermetic-local.sh

e2e-hermetic-inner:
    CARGO_NET_OFFLINE=true AXON_E2E_HERMETIC=1 AXON_E2E_LIVE=0 AXON_E2E_PROVIDER_MODE=double AXON_E2E_STAGE_GATES=1 AXON_E2E_NETWORK_POLICY=loopback-only AXON_E2E_EVIDENCE_POLICY=sanitized-only python3 scripts/e2e/run-hermetic.py --report target/e2e/hermetic-report.json

e2e-mutations subset="representative":
    python3 scripts/e2e/run-mutations.py --subset {{subset}}

# Reproducible local command for the bounded Linux/macOS/Windows subset. The
# canonical report is an input to release qualification; this never uses live
# providers, privileged credentials, or homelab connectivity.
e2e-platform-smoke report="target/e2e/platform-smoke-local.json":
    python3 scripts/e2e/run-platform-smoke.py --binary target/debug/axon --report {{report}} --tested-sha "$(git rev-parse HEAD)"

# Fail-closed quarantine/attempt validation and rolling reliability projection
# for any canonical report. Required test configuration starts at zero entries.
e2e-flake-governance report="target/e2e/platform-smoke-local.json" environment="local" out="target/e2e/reliability.json":
    python3 scripts/e2e/flake-governance.py --report {{report}} --environment {{environment}} --reliability-out {{out}}

# Reporting-first representative performance sample. Contended observations
# are retained as infrastructure-classified, baseline-ineligible evidence.
e2e-performance samples="5" out="target/e2e/performance/report.json" contention="--allow-contended":
    python3 scripts/e2e/measure-real-performance.py --samples {{samples}} {{contention}} --out {{out}}

# Deterministic unsigned release projection over already-saved evidence. This
# command never reruns a lane or receives live-provider/signing credentials.
e2e-qualification index evidence_root out="target/e2e/qualification.json" summary="target/e2e/qualification.md" checksums="target/e2e/SHA256SUMS":
    python3 scripts/e2e/build-qualification-manifest.py --index {{index}} --evidence-root {{evidence_root}} --out {{out}} --summary {{summary}} --checksums {{checksums}}

test-watch:
    {{rust_dev_env}}; RUST_MIN_STACK=16777216 cargo nextest run --locked -p axon-jobs -p axon-services -p axon-cli -p axon-web --no-tests=fail -E 'test(/watch/)'

mcp-smoke:
    ./scripts/test-mcp-tools-mcporter.sh

test-all:
    {{rust_dev_env}}; cargo test --all-targets --all-features --locked

web-build:
    cd apps/web && npm run build

web-check:
    cd apps/web && npm run lint
    cargo xtask check-openapi-drift

openapi-check:
    cargo xtask check-openapi-drift

# Refresh schemas and every generated Markdown projection in dependency order.
generated-contracts-refresh:
    cargo xtask generated-contracts refresh

# Verify schema provenance and all dependent generated docs without writing.
generated-contracts-check:
    cargo xtask generated-contracts check

# Package the Chrome extension into dist/axon-page-scraper-<version>.zip
package-extension:
    ./apps/chrome-extension/package.sh

fmt:
    cargo fmt --all

fmt-check:
    cargo fmt --all -- --check

clippy:
    {{rust_dev_env}}; cargo clippy --all-targets --locked -- -D warnings

# Fail on unused crate dependencies (keeps per-crate manifests honest after the
# workspace extraction). Graceful no-op when cargo-machete is not installed.
machete:
    @if command -v cargo-machete >/dev/null 2>&1; then cargo machete; else echo "skip: cargo-machete not installed (run: mise install cargo:cargo-machete)"; fi

build:
    {{rust_dev_env}}; cargo build --profile {{local_release_profile}} --locked
    just link-bin {{local_release_profile}}

release-build:
    {{rust_dev_env}}; cargo build --release --locked
    just link-bin release

debug:
    {{rust_dev_env}}; cargo build --locked --bin axon

# Install a built binary into ~/.local/bin/axon. Local recipes default to the
# release-fast profile; pass `release` for real release artifacts.
link-bin profile=local_release_profile:
    #!/usr/bin/env bash
    set -euo pipefail
    profile="{{profile}}"
    profile_dir="$profile"
    if [ "$profile" = "release" ]; then
      profile_dir="release"
    fi
    AXON_TARGET_DIR="${CARGO_TARGET_DIR:-target}"
    case "$AXON_TARGET_DIR" in
      /*) AXON_BIN="$AXON_TARGET_DIR/$profile_dir/axon" ;;
      *)  AXON_BIN="$(pwd)/$AXON_TARGET_DIR/$profile_dir/axon" ;;
    esac
    if [ ! -x "$AXON_BIN" ]; then
      echo "$profile binary not found at $AXON_BIN — run 'just build' first" >&2
      exit 1
    fi
    variant="$profile"
    if [ "$profile" = "release-fast" ]; then
      variant="fast-release"
    fi
    mkdir -p bin
    cp -f "$AXON_BIN" "bin/axon-$variant"
    chmod 755 "bin/axon-$variant"
    mkdir -p ~/.local/bin
    ln -sf "$AXON_BIN" ~/.local/bin/axon
    systemctl --user restart axon-mcp 2>/dev/null || true
    echo "axon → $AXON_BIN"
    echo "artifact → bin/axon-$variant"

install:
    {{rust_dev_env}}; cargo build --profile {{local_release_profile}} --locked
    just link-bin {{local_release_profile}}

install-release:
    {{rust_dev_env}}; cargo build --release --locked
    just link-bin release

# Build the local dev runtime image from this checkout.
container-build:
    #!/usr/bin/env bash
    set -euo pipefail
    source scripts/lib/axon-env.sh
    repo="$(pwd)"
    env_file="$(resolve_axon_env_file "$repo")"
    compose=(docker compose)
    if [ -f "$env_file" ]; then
      compose+=(--env-file "$env_file")
    fi
    compose+=(-f docker-compose.yaml)
    "${compose[@]}" build axon

# Recreate the axon service with the locally built debug binary bind-mounted.
container-up:
    #!/usr/bin/env bash
    set -euo pipefail
    source scripts/lib/axon-env.sh
    repo="$(pwd)"
    env_file="$(resolve_axon_env_file "$repo")"
    cargo build --locked --bin axon
    compose=(docker compose)
    if [ -f "$env_file" ]; then
      compose+=(--env-file "$env_file")
    fi
    export AXON_DEV_TARGET_DIR="${CARGO_TARGET_DIR:-$repo/target}/debug"
    compose+=(-f docker-compose.yaml)
    "${compose[@]}" up -d axon --no-deps
    "${compose[@]}" ps axon

# Build local release-fast binary when stale, sync PATH symlinks, refresh local dev runtime if needed, restart container.
# Synchronous version of what `scripts/axon` does automatically in the background.
sync-container:
    #!/usr/bin/env bash
    set -euo pipefail
    source scripts/lib/axon-env.sh
    repo="$(pwd)"
    load_axon_env_file "$repo"
    env_file="$(resolve_axon_env_file "$repo")"
    if command -v mold >/dev/null 2>&1; then
      export RUSTFLAGS="${RUSTFLAGS:-} -C link-arg=-fuse-ld=mold"
    fi

    profile="{{local_release_profile}}"
    AXON_TARGET_DIR="${CARGO_TARGET_DIR:-target}"
    case "$AXON_TARGET_DIR" in
      /*) AXON_BIN="$AXON_TARGET_DIR/$profile/axon" ;;
      *) AXON_BIN="$repo/$AXON_TARGET_DIR/$profile/axon" ;;
    esac

    release_stale=0
    if [ ! -x "$AXON_BIN" ]; then
      release_stale=1
    else
      while IFS= read -r -d '' input; do
        if [ "$input" -nt "$AXON_BIN" ]; then
          release_stale=1
          break
        fi
      done < <(git ls-files -z -- Cargo.toml Cargo.lock rust-toolchain.toml .cargo build.rs src crates config.example.toml config migrations apps/web/out assets)
    fi
    if [ "$release_stale" -eq 1 ]; then
      cargo build --profile "$profile" --locked --bin axon
    else
      echo "release binary is current: $AXON_BIN"
    fi

    mkdir -p ~/.local/bin
    ln -sf "$AXON_BIN" ~/.local/bin/axon
    systemctl --user restart axon-mcp 2>/dev/null || true
    echo "axon -> $AXON_BIN"

    compose=(docker compose)
    if [ -f "$env_file" ]; then
      compose+=(--env-file "$env_file")
    fi
    export AXON_DEV_TARGET_DIR="$(dirname "$AXON_BIN")"
    compose+=(-f docker-compose.yaml)
    container_sentinel="$AXON_TARGET_DIR/.container-built"
    image_stale=0
    if ! docker image inspect axon:dev-runtime >/dev/null 2>&1; then
      image_stale=1
    else
      while IFS= read -r -d '' input; do
        if [ "$input" -nt "$container_sentinel" ] 2>/dev/null; then
          image_stale=1
          break
        fi
      done < <(git ls-files -z -- config/Dockerfile docker-compose.prod.yaml docker-compose.yaml config/chrome)
    fi
    if [ "$image_stale" -eq 1 ]; then
      "${compose[@]}" build axon
      touch "$container_sentinel"
      "${compose[@]}" up -d axon --no-deps
    else
      echo "dev runtime image is current"
      "${compose[@]}" up -d axon --no-deps --no-build
    fi
    "${compose[@]}" restart axon
    "${compose[@]}" ps axon
    echo "container synced"

container-sync: sync-container

install-debug:
    #!/usr/bin/env bash
    set -euo pipefail
    mkdir -p ~/.local/bin
    AXON_TARGET_DIR="${CARGO_TARGET_DIR:-target}"
    case "$AXON_TARGET_DIR" in
      /*) AXON_BIN="$AXON_TARGET_DIR/debug/axon" ;;
      *) AXON_BIN="$(pwd)/$AXON_TARGET_DIR/debug/axon" ;;
    esac
    stale=0
    if [ ! -x "$AXON_BIN" ]; then
      stale=1
    else
      while IFS= read -r -d '' input; do
        if [ "$input" -nt "$AXON_BIN" ]; then
          stale=1
          break
        fi
      done < <(git ls-files -z -- Cargo.toml Cargo.lock rust-toolchain.toml .cargo src crates config.example.toml docker-compose.prod.yaml docker-compose.yaml config migrations)
    fi
    if [ "$stale" -eq 1 ]; then
      just debug
    else
      echo "debug binary is current: $AXON_BIN"
    fi
    mkdir -p ~/.local/bin
    ln -sf "$AXON_BIN" ~/.local/bin/axon
    systemctl --user restart axon-mcp 2>/dev/null || true
    echo "axon → $AXON_BIN"

lint-all:
    just fmt-check
    just clippy

legacy-runtime-check:
    ./scripts/check_legacy_runtime_terms.sh

# Web acquisition must go through axon_core::http::fetch_web (one ladder,
# one place to fix). Fails on a new client built outside the allowlist.
fetch-divergence-check:
    cargo xtask check-fetch-divergence

# TEST-M2 / PERF-C1: fail if block_on/block_in_place appears in the ask/retrieval hot path.
blocking-async-check:
    ./scripts/check_no_blocking_async.sh

primitive-inventory-check:
    python3 scripts/check_aurora_primitive_inventory.py

validate-plugin:
    python3 scripts/validate_plugin.py

# Machine-readable operational-test catalog. The token following each path is
# the execution class consumed by repository contract review.
# test-catalog: scripts/test-axon-env.sh hermetic-required
# test-catalog: scripts/test-axon-wrapper-fast-path.sh hermetic-required
# test-catalog: scripts/test-axon-backup.sh hermetic-required
# test-catalog: scripts/test-bench-source-pipeline.sh hermetic-required
# test-catalog: scripts/test-chrome-extension-agent-os.sh live-optional
# test-catalog: scripts/test-evaluate-retrieval.sh hermetic-required
# test-catalog: scripts/test-install-behavior.sh platform-required:linux
# test-catalog: scripts/test-incus-bootstrap.sh hermetic-required
# test-catalog: scripts/test-review-remediation.py hermetic-required
# test-catalog: scripts/test-install-windows.ps1 platform-required:windows
# test-catalog: scripts/test-live-cli-portability.sh hermetic-required
# test-catalog: scripts/test-mcp-tasks-wire.py live-optional
# test-catalog: scripts/test-mlx-metrics.py hermetic-required
# test-catalog: scripts/test_env_config_boundary.py hermetic-required
# test-catalog: scripts/test_mcp_doc_renderer.py hermetic-required
# test-catalog: scripts/test_qdrant_quality.py hermetic-required
# test-catalog: scripts/test_qdrant_tune.py hermetic-required
# test-catalog: scripts/test_tei_tune.py hermetic-required
# test-catalog: scripts/test_dockerfile_supply_chain.py hermetic-required
# test-catalog: scripts/test_operational_docs.py hermetic-required
# test-catalog: scripts/test-with-timeout.sh hermetic-required
operational-test-contracts:
    scripts/test-axon-env.sh
    scripts/test-axon-wrapper-fast-path.sh
    scripts/test-axon-backup.sh
    scripts/test-bench-source-pipeline.sh
    scripts/test-evaluate-retrieval.sh
    if [ "$(uname -s)" = "Linux" ]; then scripts/test-install-behavior.sh; else echo "SKIP test-install-behavior.sh (Linux platform required)"; fi
    scripts/test-incus-bootstrap.sh
    python3 scripts/test-review-remediation.py
    scripts/test-live-cli-portability.sh
    python3 scripts/test-mlx-metrics.py
    python3 scripts/test_env_config_boundary.py
    python3 scripts/test_mcp_doc_renderer.py
    python3 scripts/test_qdrant_quality.py
    python3 scripts/test_qdrant_tune.py
    python3 scripts/test_tei_tune.py
    python3 scripts/test_dockerfile_supply_chain.py
    python3 scripts/test_operational_docs.py
    scripts/test-with-timeout.sh

runtime-current:
    ./scripts/axon doctor

verify:
    just legacy-runtime-check
    just fetch-divergence-check
    just blocking-async-check
    just primitive-inventory-check
    just validate-plugin
    just operational-test-contracts
    just web-check
    just fmt-check
    just clippy
    just machete
    just check
    just test

ci:
    just verify

precommit:
    cargo xtask check-secrets
    python3 scripts/check_compose_port_bindings.py --staged
    python3 scripts/enforce_no_legacy_symbols.py
    just legacy-runtime-check
    just fetch-divergence-check
    just primitive-inventory-check
    if [ -f "$HOME/.claude/hooks/enforce_monoliths.py" ]; then python3 "$HOME/.claude/hooks/enforce_monoliths.py" --staged; elif [ -f "scripts/enforce_monoliths.py" ]; then python3 scripts/enforce_monoliths.py --staged; else echo "ERROR: enforce_monoliths.py not found" && exit 1; fi
    just fmt-check
    just clippy
    just check
    just test

# Whole-repo monolith size report (informational, exits 0).
# Lists every oversized file/function not already in .monolith-allowlist.
# Pass --include-allowlisted to also surface allowlisted entries.
monolith-report *ARGS:
    python3 scripts/enforce_monoliths.py --whole-repo {{ARGS}}

fix:
    cargo fmt --all
    {{rust_dev_env}}; cargo clippy --fix --all-targets --locked --allow-dirty --allow-staged

fix-all:
    just fix

taplo-check:
    if command -v taplo >/dev/null 2>&1; then taplo fmt --check; else echo "taplo not installed. Run: mise install cargo:taplo-cli"; exit 1; fi

taplo-fmt:
    if command -v taplo >/dev/null 2>&1; then taplo fmt; else echo "taplo not installed. Run: mise install cargo:taplo-cli"; exit 1; fi

coverage-branch:
    if cargo llvm-cov --version >/dev/null 2>&1; then {{rust_dev_env}}; cargo llvm-cov --locked --workspace --all-features --lcov --output-path .cache/coverage/lcov.info; else echo "cargo-llvm-cov not installed. Run: mise install cargo:cargo-llvm-cov"; exit 1; fi

# ── Codegen ───────────────────────────────────────────────────

gen-mcp-schema *ARGS:
    python3 scripts/generate_mcp_schema_doc.py {{ARGS}}

clean:
    cargo clean

# Backward-compatible, self-contained local infrastructure default.
services-up:
    just services-up-local

services-up-local:
    just ensure-compose-network
    docker compose --env-file "${AXON_ENV_FILE:-$HOME/.axon/.env}" -f docker-compose.yaml --profile local-qdrant up -d axon-qdrant axon-tei axon-chrome

services-up-external-qdrant:
    #!/usr/bin/env bash
    set -euo pipefail
    just ensure-compose-network
    source scripts/lib/axon-env.sh
    repo="$(pwd)"
    load_axon_env_file "$repo"
    : "${AXON_EXTERNAL_QDRANT_URL:?AXON_EXTERNAL_QDRANT_URL must be set}"
    env_file="$(resolve_axon_env_file "$repo")"
    echo "external Qdrant: $AXON_EXTERNAL_QDRANT_URL"
    docker compose --env-file "$env_file" -f docker-compose.yaml up -d axon-tei axon-chrome

# Stop infrastructure services
services-down:
    docker compose --env-file "${AXON_ENV_FILE:-$HOME/.axon/.env}" -f docker-compose.yaml --profile local-qdrant stop axon-qdrant axon-tei axon-chrome
    docker compose --env-file "${AXON_ENV_FILE:-$HOME/.axon/.env}" -f docker-compose.yaml --profile local-qdrant rm -f axon-qdrant axon-tei axon-chrome
    just remove-compose-network-if-owned

# Start/stop an explicitly local Qdrant. Use this only for local test data or
# when AXON_QDRANT_URL=http://axon-qdrant:6333 is set for the axon container.
qdrant-up:
    just ensure-compose-network
    docker compose --env-file "${AXON_ENV_FILE:-$HOME/.axon/.env}" -f docker-compose.yaml --profile local-qdrant up -d axon-qdrant

qdrant-down:
    docker compose --env-file "${AXON_ENV_FILE:-$HOME/.axon/.env}" -f docker-compose.yaml --profile local-qdrant stop axon-qdrant
    docker compose --env-file "${AXON_ENV_FILE:-$HOME/.axon/.env}" -f docker-compose.yaml --profile local-qdrant rm -f axon-qdrant
    just remove-compose-network-if-owned

# Production stack (docker-compose.prod.yaml), bundled qdrant mode — the default.
# Every invocation guarantees --env-file so .env's values actually reach
# Compose interpolation (a bare `docker compose up` from the wrong cwd can
# miss the file entirely). Note this guarantees .env is READ, not that no
# default-drift is possible: docker-compose.prod.yaml's own
# TEI_MAX_CONCURRENT_REQUESTS:-512 fallback is a separate value from TEI's own
# built-in default — this repo has hit that exact two-layer drift before
# (32 vs 256 permits) and --env-file alone doesn't prevent a repeat if the
# YAML's own fallback and .env's intended value ever diverge again.
ensure-compose-network:
    #!/usr/bin/env bash
    set -euo pipefail
    source scripts/lib/axon-env.sh
    repo="$(pwd)"
    load_axon_env_file "$repo"
    network="${DOCKER_NETWORK:-axon}"
    if docker network inspect "$network" >/dev/null 2>&1; then
      driver="$(docker network inspect "$network" | python3 -c 'import json,sys; print(json.load(sys.stdin)[0]["Driver"])')"
      [ "$driver" = bridge ] || { echo "network $network exists with incompatible driver $driver" >&2; exit 1; }
    else
      docker network create --driver bridge --label ai.dinglebear.axon.network-owner=preflight "$network" >/dev/null
    fi

remove-compose-network-if-owned:
    #!/usr/bin/env bash
    set -euo pipefail
    source scripts/lib/axon-env.sh
    repo="$(pwd)"
    load_axon_env_file "$repo"
    network="${DOCKER_NETWORK:-axon}"
    owner="$(docker network inspect "$network" 2>/dev/null | python3 -c 'import json,sys; data=json.load(sys.stdin); print((data[0].get("Labels") or {}).get("ai.dinglebear.axon.network-owner", ""))' || true)"
    if [ "$owner" = preflight ]; then
      docker network rm "$network" >/dev/null
    fi

prod-up:
    #!/usr/bin/env bash
    set -euo pipefail
    just ensure-compose-network
    source scripts/lib/axon-env.sh
    repo="$(pwd)"
    env_file="$(resolve_axon_env_file "$repo")"
    if [ -f "$env_file" ]; then
      perm=$(stat -c '%a' "$env_file" 2>/dev/null || stat -f '%Lp' "$env_file")
      if [ "${perm: -2}" != "00" ]; then
        echo "warn: $env_file is group/world-readable (mode $perm) — tighten with chmod 600" >&2
      fi
    fi
    echo "=== bundled qdrant IS starting locally (default mode) ==="
    docker compose --env-file "$env_file" -f docker-compose.prod.yaml up -d

prod-down:
    #!/usr/bin/env bash
    set -euo pipefail
    source scripts/lib/axon-env.sh
    env_file="$(resolve_axon_env_file "$(pwd)")"
    docker compose --env-file "$env_file" -f docker-compose.prod.yaml down
    just remove-compose-network-if-owned

# Production stack, external-qdrant override — this deployment's mode (qdrant
# lives on tootie). Requires AXON_EXTERNAL_QDRANT_URL; fails loudly if unset.
prod-up-external-qdrant:
    #!/usr/bin/env bash
    set -euo pipefail
    just ensure-compose-network
    source scripts/lib/axon-env.sh
    repo="$(pwd)"
    load_axon_env_file "$repo"
    env_file="$(resolve_axon_env_file "$repo")"
    echo "=== bundled qdrant is NOT starting, using external QDRANT_URL=${AXON_EXTERNAL_QDRANT_URL:?AXON_EXTERNAL_QDRANT_URL must be set} ==="
    docker compose --env-file "$env_file" -f docker-compose.prod.yaml -f docker-compose.external-qdrant.yaml up -d

prod-down-external-qdrant:
    #!/usr/bin/env bash
    set -euo pipefail
    source scripts/lib/axon-env.sh
    env_file="$(resolve_axon_env_file "$(pwd)")"
    docker compose --env-file "$env_file" -f docker-compose.prod.yaml -f docker-compose.external-qdrant.yaml down
    just remove-compose-network-if-owned

# Backward-compatible aliases used by setup/docs for local infra.
test-infra-up:
    just services-up

test-infra-down:
    just services-down

watch-check:
    cargo watch -x 'check -q --locked' -x 'check -q --tests --locked' -x 'test -q --lib --locked'

rebuild:
    just check
    just test

# ── Local dev ────────────────────────────────────────────────────

# Kill any running axon mcp or workers
stop:
    -pkill -f 'axon.*(mcp|jobs worker)' 2>/dev/null || true
    @echo "Stopped running servers and workers"

# Start local infra (TEI, Chrome), then run axon mcp as the worker daemon.
# Qdrant is expected at AXON_QDRANT_URL/QDRANT_URL, defaulting to tootie.
# Fire-and-forget CLI jobs require axon mcp running to be processed.
dev:
    #!/usr/bin/env bash
    set -euo pipefail
    just stop
    sleep 1
    export RUST_LOG="${RUST_LOG:-info,axon.mcp.oauth=info,axon::crates::mcp=info}"
    {{rust_dev_env}};
    cargo build --locked --bin axon
    AXON_BIN="${CARGO_TARGET_DIR:-$(pwd)/target}/debug/axon"
    docker compose --env-file "${AXON_ENV_FILE:-$HOME/.axon/.env}" -f docker-compose.yaml up -d --wait axon-tei axon-chrome
    "$AXON_BIN" mcp

# ── Perf bench ────────────────────────────────────────────────────────────────

# Run the ask perf bench harness. Defaults: 30 runs, both cold+warm modes.
# See docs/perf/README.md for sample-size guidance and prereqs.
bench-ask runs="30" mode="cold":
    @bash scripts/bench-ask.sh --runs {{runs}} --mode {{mode}}