set shell := ["bash", "-euo", "pipefail", "-c"]

# List available recipes
default:
    @just --list

# Enter nix development shell (just is aliased to use Justfile.nix inside)
nix:
    nix develop

# Activate the in-repo git hooks (.githooks/) for this checkout.
# Currently installs a commit-msg hook that rejects AI-attribution
# trailers (`Co-Authored-By: Claude`, etc.). Run once per clone.
setup-hooks:
    git config core.hooksPath .githooks
    @echo "Hooks active: .githooks/"

# Format Rust code
fix-fmt:
    cargo fmt --all

# Check Rust formatting
check-fmt:
    cargo fmt --all --check

# Format TOML files
fix-taplo:
    taplo fmt **/Cargo.toml Cargo.toml

# Check TOML formatting
check-taplo:
    taplo fmt **/Cargo.toml Cargo.toml --check

# --- Linting ---

# Run cargo check
check:
    cargo check --workspace --all-features --all-targets

# Fix clippy warnings automatically
fix-clippy:
    cargo clippy --workspace --no-deps --all-features --all-targets --all --fix --allow-dirty --allow-staged -- -D warnings

# Check clippy lints
check-clippy:
    cargo clippy --workspace --no-deps --all-features --all-targets --all -- -D warnings

# Check for unused dependencies
check-udeps:
    cargo +nightly udeps --workspace --all-features --all-targets

# Run all custom code-audit linters under `linters/`.
#
# Each linter under `linters/` is its own binary crate; this recipe
# invokes every one in turn against the repo root. Linters print
# summary counts to stdout and write detailed reports to
# `linters/*-report.txt`.
#
# Per Phase 0c (D-EXP.8): both linters run unconditionally even if a
# predecessor exits non-zero, accumulating the worst status into the
# recipe's exit code. This means a failing linter does NOT mask
# failures in subsequent linters — the CI lints job sees the full
# picture in one invocation.
#
# Per Phase 0c (D-EXP.7/D-EXP.8): `check-lints` is intentionally NOT
# in the `ci` aggregate below — the `lints` GitHub Actions job
# invokes this recipe in parallel with `just ci`, so a linter
# failure does not block `check-fmt` / `check-clippy` / `check-audit`
# / `check-deny` / `test` from executing.
check-lints:
    #!/usr/bin/env bash
    set -uo pipefail
    status=0
    cargo run --manifest-path linters/Cargo.toml -p non-result-fns || status=$?
    cargo run --manifest-path linters/Cargo.toml -p expect-allow-ban || status=$?
    exit $status

# --- Testing ---

# Run the full aggregated rudzio suite via the auto-generated runner.
# Uses `cargo run -p cargo-rudzio -- test` so the recipe works on a fresh
# clone without requiring `cargo install cargo-rudzio`.
# `--threads-parallel-hardlimit=none` disables the process-wide parallel
# gate so concurrent-heavy suites don't park on it during local/CI runs.
test:
    cargo run -p cargo-rudzio -- test -- --threads-parallel-hardlimit=none

# --- Security & policy ---

# Check for security advisories in the dep graph (RustSec)
check-audit:
    cargo audit

# Check license / advisory / source / banned-crate policy (deny.toml)
check-deny:
    cargo deny check

# Check API semver compatibility against the most recent crates.io release
check-semver:
    cargo semver-checks check-release --workspace \
        --exclude rudzio-fixtures

# --- CI/CD ---

# Recent CI runs
ci-status:
    gh run list --workflow=ci.yml --limit 10

# Trigger CI on the current branch
ci-trigger:
    gh workflow run ci.yml --ref "$(git rev-parse --abbrev-ref HEAD)"

# Watch the most recent CI run
ci-watch:
    gh run watch

# Recent release runs
release-status:
    gh run list --workflow=release.yml --limit 10

# Watch the most recent release run
release-watch:
    gh run watch

# Dry-run `cargo publish` in dep order — sanity check before tagging.
# Pre-first-publish: each crate's dry-run will fail at the verify step
# because cargo strips path deps and tries to resolve workspace siblings
# from crates.io. Use `--no-verify` to package-only check, or run live
# in order via the release.yml workflow on a tag push.
release-dry-run:
    cargo publish --dry-run --no-verify -p rudzio-macro-internals
    cargo publish --dry-run --no-verify -p rudzio-macro
    cargo publish --dry-run --no-verify -p rudzio
    cargo publish --dry-run --no-verify -p rudzio-migrate
    cargo publish --dry-run --no-verify -p cargo-rudzio

# Tag current commit and instruct on push, e.g. `just release-tag 0.2.0`
release-tag VERSION:
    git tag -a "v{{VERSION}}" -m "Release v{{VERSION}}"
    @echo "Created tag v{{VERSION}}. Push it to trigger crates.io publish:"
    @echo "    git push origin v{{VERSION}}"

# --- Demo ---

# Regenerate assets/demo.gif from assets/demo.sh.
#   * asciinema records the script to a .cast file (terminal-native, no
#     headless browser involved — works on minimal nix shells).
#   * agg (asciinema-agg) renders the cast as a gif.
# Requires asciinema and agg on PATH. The gif is committed so consumers
# don't need either tool to view the README.
demo:
    @command -v asciinema >/dev/null || { echo "missing: asciinema"; exit 1; }
    @command -v agg       >/dev/null || { echo "missing: agg (asciinema-agg)"; exit 1; }
    @# Make sure `cargo rudzio` resolves as a cargo subcommand during the
    @# recording — otherwise the demo would have to fall back to `cargo run`.
    cargo install --path cargo-rudzio --locked --quiet
    asciinema rec --quiet --overwrite \
        --cols 140 --rows 40 \
        --command 'bash assets/demo.sh' \
        /tmp/rudzio-demo.cast
    agg --speed 2.0 --theme monokai --cols 140 --rows 40 \
        /tmp/rudzio-demo.cast assets/demo.gif
    rm -f /tmp/rudzio-demo.cast
    @echo "→ assets/demo.gif regenerated ($(du -h assets/demo.gif | cut -f1))"

# --- Aggregate ---

# Apply all automatic fixes
fix: fix-fmt fix-taplo fix-clippy

# Run all checks and tests (pre-commit)
pre-commit: check-fmt check-taplo check check-clippy check-udeps test

# One command for the entire CI/CD pipeline — every gate the
# self-hosted runner executes on push to main runs here. `check-semver`
# is intentionally NOT in this aggregate until the first crates.io
# publish creates a baseline (it errors with "not found in registry"
# pre-publish). After v0.1.0 lands, add `check-semver` to this list.
#
# Per Phase 0c (D-EXP.8): `check-lints` is intentionally REMOVED
# from this aggregate. The CI workflow runs `just check-lints` as a
# separate parallel job (see `.github/workflows/ci.yml`) so a linter
# failure cannot block any other gate from executing.
ci: check-fmt check-taplo check-clippy check-audit check-deny test
