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-.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}}