# Local dev recipes. Bare `just` lists them. No one-shot aliases over # raw cargo commands - recipes only exist when they compose, set up # env, or dispatch on a mode. set shell := ["bash", "-euo", "pipefail", "-c"] set tempdir := "/tmp" _default: @just --list # Static source/tooling checks. Does not run behavior tests. # The round-trip gate fails when this branch adds/changes a serializer in src/ # without a paired round-trip test; see scripts/check-roundtrip-gate.sh. static-checks: cargo fmt --all --check cargo clippy --all-targets --all-features -- -D warnings cargo clippy --all-targets --no-default-features -- -D warnings RUSTDOCFLAGS="-Dwarnings" cargo doc --no-deps --all-features cargo fetch --locked cargo audit --deny warnings just lint-workflows just lint-scripts scripts/check-contracts typos bash scripts/check-roundtrip-gate.sh python3 scripts/fixtures/check_live_selection.py # Optional local offline gate for diagnosis or work without hosted CI. gate: just static-checks just test offline # Check GitHub workflow helpers with the repo hardening guard plus optional # actionlint when it is installed locally. lint-workflows: #!/usr/bin/env bash set -euo pipefail pycache_dir="${PYTHONPYCACHEPREFIX:-/tmp/codex/kei/pycache}" mkdir -p "$pycache_dir" python3 .github/scripts/check_workflow_hardening.py PYTHONPYCACHEPREFIX="$pycache_dir" python3 -m py_compile .github/scripts/*.py if command -v actionlint >/dev/null 2>&1; then actionlint .github/workflows/*.yml else echo "lint-workflows: actionlint not installed; skipping optional workflow syntax lint" >&2 fi # Check local shell and Python helpers. External format/lint tools are # check-only and optional locally so missing developer tools don't break the # baseline gate. lint-scripts: #!/usr/bin/env bash set -euo pipefail pycache_dir="${PYTHONPYCACHEPREFIX:-/tmp/codex/kei/pycache}" mkdir -p "$pycache_dir" mapfile -t shell_files < <(find scripts tests/shell docker -maxdepth 3 -type f \( -name '*.sh' -o -name 'entrypoint.sh' \) -print | sort) mapfile -t python_files < <(find scripts .github/scripts -maxdepth 3 -type f -name '*.py' -print | sort) python_files+=(scripts/check-contracts) for shell_file in "${shell_files[@]}"; do bash -n "$shell_file" done PYTHONPYCACHEPREFIX="$pycache_dir" python3 -m py_compile "${python_files[@]}" if command -v shellcheck >/dev/null 2>&1; then shellcheck -x -P tests/shell:scripts:scripts/full-test "${shell_files[@]}" else echo "lint-scripts: shellcheck not installed; skipping optional shell lint" >&2 fi if command -v shfmt >/dev/null 2>&1; then shfmt -d "${shell_files[@]}" else echo "lint-scripts: shfmt not installed; skipping optional shell format check" >&2 fi if command -v ruff >/dev/null 2>&1; then ruff check "${python_files[@]}" else echo "lint-scripts: ruff not installed; skipping optional Python lint" >&2 fi # Live provider and real host-service evidence. Requires explicit service authorization. release-local: bash scripts/full-test/run_local_release.sh # Pre-release battery with phase logs, metrics, Docker smokes, and live smokes. # Stops on first failure. Keeps a /tmp/codex/kei/full-test/logs/kei-full-test-*.log only when failing. full-test: #!/usr/bin/env bash set -Eeuo pipefail log_dir="${KEI_FULLTEST_LOG_DIR:-/tmp/codex/kei/full-test/logs}" mkdir -p "$log_dir" log_path=$(mktemp "$log_dir/kei-full-test-$(date +%Y%m%dT%H%M%S)-XXXXXX.log") failed_line="" failed_command="" record_failure() { failed_line="${BASH_LINENO[0]:-unknown}" failed_command="$BASH_COMMAND" } finish() { rc=$? if [ "$rc" -eq 0 ]; then rm -f "$log_path" return 0 fi { echo "" echo "full-test: failed with exit code $rc" if [ -n "$failed_command" ]; then echo "full-test: failed command near line $failed_line: $failed_command" fi echo "full-test: log saved at $log_path" } >&2 exit "$rc" } trap record_failure ERR trap finish EXIT exec > >(tee "$log_path") 2>&1 echo "full-test: keeping failure log at $log_path if this run fails" scripts/full-test/run_all.sh # Compact history table for previous `just full-test` runs. full-test-history N="10": scripts/full-test/history.sh "{{N}}" # Compact agent preflight: branch, worktree, diff size, and latest full-test record. agent-status: #!/usr/bin/env bash set -euo pipefail echo "repo: $(pwd)" echo git status --short --branch echo echo "diff stat:" if git diff --quiet -- . && git diff --cached --quiet -- .; then echo "(clean)" else git diff --stat git diff --cached --stat fi echo runs_dir="${KEI_FULL_TEST_RUNS_DIR:-/tmp/codex/kei/full-test/test-runs}" latest=$(find "$runs_dir" -maxdepth 1 -name '*.json' -type f -printf '%T@ %p\n' 2>/dev/null | sort -nr | head -n 1 | cut -d' ' -f2- || true) echo "latest full-test:" if [[ -n "$latest" ]]; then echo "$latest" if rg -q '"status": "fail"' "$latest"; then result=fail else result=pass fi scripts/full-test/render_summary.py "$latest" --result "$result" | sed -n '1,80p' else echo "(no run records found)" fi echo scripts/just/review-scope.sh --validation-only echo echo "recent full-test history:" scripts/full-test/history.sh 3 2>/dev/null || true # Exact branch review input plus workspace and validation provenance. review-scope BASE="origin/main": scripts/just/review-scope.sh "{{BASE}}" # Summarize the latest full-test phase log, or pass LOG=/path/to/log. agent-failure-summary LOG="": #!/usr/bin/env bash set -euo pipefail log="{{LOG}}" log="${log#LOG=}" if [[ -z "$log" ]]; then log_dir="${KEI_FULLTEST_LOG_DIR:-/tmp/codex/kei/full-test/logs}" log=$(find "$log_dir" -maxdepth 1 -type f \( -name 'full-test-*.log' -o -name 'kei-full-test-*.log' \) -printf '%T@ %p\n' 2>/dev/null | sort -nr | head -n 1 | cut -d' ' -f2- || true) fi if [[ -z "$log" || ! -f "$log" ]]; then echo "agent-failure-summary: no log found" >&2 exit 1 fi echo "log: $log" echo echo "failure markers:" rg -n "FAILED|failures:|error:|panicked|PermissionDenied|Operation not permitted|Failed to bind|Could not resolve|timed out|timeout|rc=[1-9]" "$log" | tail -n 80 || echo "(no common failure markers found)" echo echo "tail:" tail -n 80 "$log" # Check lightweight source CONTRACT markers against their contract_ tests. check-contracts: scripts/check-contracts # Fast offline v0.20 patch-release smoke for the May 27 regression set. release-smoke: scripts/full-test/run_release_regression_smoke.sh # Test dispatcher: offline | fast | stress | scenario NAME | scenarios | live | live-smoke | live-shell | packaging | docker-full | service | host-service | PATTERN. test MODE="" *ARGS="": #!/usr/bin/env bash set -euo pipefail _live_env() { source scripts/just/live-env.sh } run_scenario() { local name="$1" local script="scripts/test-scenarios/$name.sh" if [[ ! -f "$script" ]]; then echo "unknown scenario: $name" >&2 echo "available scenarios:" >&2 scripts/test-scenarios/list.sh >&2 exit 2 fi bash "$script" } run_live_smokes() { scripts/full-test/run_live_smokes.sh scripts/full-test/run_live_import_rehearsal.sh if [[ -n "${KEI_FULL_TEST_CROSS_ZONE_ALBUM:-}" ]]; then scripts/full-test/run_cross_zone_album_hydration.sh fi } run_docker_full() { just docker build scripts/full-test/run_docker_puid_smoke.sh scripts/full-test/run_docker_packaging_smoke.sh just docker multiarch docker run --rm "${KEI_DOCKER_IMAGE:-kei:dev}" --version docker run --rm "${KEI_DOCKER_IMAGE:-kei:dev}" --help set +e timeout 8 docker run --rm -e ICLOUD_USERNAME=dummy@example.com "${KEI_DOCKER_IMAGE:-kei:dev}" rc=$? set -e [[ $rc -ne 2 ]] } mode="{{MODE}}" case "$mode" in "") cargo test --all-features ;; stress) bash scripts/test-copies-stress.sh ;; fast) cargo test --lib -- --test-threads=1 cargo test --test cli --test behavioral --test service_cli --test service_linux --test service_macos --test service_windows --test service_status ;; offline) cargo test --all-features cargo test --no-default-features scripts/test-scenarios/check.sh ;; scenarios) scenarios=$(scripts/test-scenarios/list.sh) [[ -n "$scenarios" ]] || { echo "no scenarios found" >&2; exit 2; } while read -r scenario; do run_scenario "$scenario" done <<<"$scenarios" ;; scenario) args=({{ARGS}}) if [[ ${#args[@]} -ne 1 ]]; then echo "usage: just test scenario NAME" >&2 scripts/test-scenarios/list.sh >&2 exit 2 fi run_scenario "${args[0]}" ;; scenario-*) run_scenario "${mode#scenario-}" ;; live) _live_env cargo test --all-features --lib icloud::photos::album::lookup::tests::live_targeted_record_lookup_distinguishes_present_and_missing -- --exact --ignored --test-threads=1 cargo test --all-features --test sync -- --ignored --test-threads=1 cargo test --all-features --test state_auth -- --ignored --test-threads=1 cargo test --all-features --test import_existing_live -- --ignored --test-threads=1 ;; live-smoke) _live_env run_live_smokes ;; live-shell) _live_env scripts/full-test/run_shell_suites.sh ;; concurrency) _live_env ./tests/shell/concurrency.sh ;; state) _live_env ./tests/shell/state-machine.sh ;; docker) _live_env ./tests/shell/docker.sh ;; packaging|package) cargo build --release scripts/full-test/run_release_archive_smoke.sh bash scripts/fixtures/check-package.sh ;; docker-full) run_docker_full ;; service) just service-smoke ;; host-service) KEI_FULL_TEST_REAL_SERVICE=1 scripts/full-test/run_real_service_lifecycle.sh ;; nightly-tools) if rustup toolchain list 2>/dev/null | grep -q '^nightly' && command -v cargo-fuzz >/dev/null 2>&1; then cargo +nightly fuzz build else echo "nightly-tools: skipping fuzz build; nightly toolchain or cargo-fuzz not installed" >&2 fi cargo +nightly --version >/dev/null cargo udeps --version >/dev/null cargo +nightly udeps --all-targets ;; *) cargo test "$mode" ;; esac # Coverage: (none) | html | check | live | patch [BASE]. `live` merges sync + state_auth into the offline baseline. cov MODE="" BASE="main": #!/usr/bin/env bash set -euo pipefail _live_env() { source scripts/just/live-env.sh } case "{{MODE}}" in "") cargo llvm-cov --all-features ;; html) cargo llvm-cov --all-features --html echo "Report: target/llvm-cov/html/index.html" ;; check) cargo llvm-cov --all-features --summary-only --fail-under-lines 90 ;; live) _live_env # --no-report accumulates coverage across multiple test # binary invocations so we can run the live suites under the # same profile data as the offline ones. The final `report` # call prints the merged summary. cargo llvm-cov clean --workspace cargo llvm-cov --no-report --lib cargo llvm-cov --no-report --test cli cargo llvm-cov --no-report --test behavioral cargo llvm-cov --no-report --test sync -- --include-ignored --test-threads=1 cargo llvm-cov --no-report --test state_auth -- --include-ignored --test-threads=1 cargo llvm-cov --no-report --test import_existing_live -- --include-ignored --test-threads=1 cargo llvm-cov report --summary-only ;; patch) cargo llvm-cov --all-features --lcov --output-path head.lcov -- --skip download::orchestration::queue_projection::tests::private_300_current_writers_and_600_aliases_finish_through_reseeds_without_repeated_writes # --detach so the base worktree doesn't collide with any # existing checkout of the base branch (e.g. the main repo # already sitting on main). git worktree add --detach ../.kei-cov-base "{{BASE}}" >/dev/null (cd ../.kei-cov-base && cargo llvm-cov --all-features --lcov --output-path "$OLDPWD/base.lcov" -- --skip download::orchestration::queue_projection::tests::private_300_current_writers_and_600_aliases_finish_through_reseeds_without_repeated_writes) git worktree remove ../.kei-cov-base >/dev/null python3 .github/scripts/patch_coverage.py \ --lcov head.lcov \ --base-lcov base.lcov \ --base "{{BASE}}" rm -f head.lcov base.lcov ;; *) echo "Unknown mode: {{MODE}}" >&2 echo "Modes: (none) | html | check | live | patch [BASE]" >&2 exit 1 ;; esac # Run any kei subcommand under cargo run with .env + temp data/photos dirs pre-applied. dev CMD *ARGS: #!/usr/bin/env bash set -euo pipefail if [ -f .env ]; then set -a; source .env; set +a fi export KEI_DATA_DIR="${KEI_DEV_DATA_DIR:-$HOME/.config/kei}" cfg="$(mktemp)" trap 'rm -f "$cfg"' EXIT printf 'data_dir = "%s"\n[download]\ndirectory = "%s"\n' "$KEI_DATA_DIR" "${KEI_DEV_PHOTOS_DIR:-/tmp/codex/kei/dev-photos}" > "$cfg" cargo run -- {{CMD}} --config "$cfg" {{ARGS}} # Docker: build | multiarch | run | shell | health. docker MODE: #!/usr/bin/env bash set -euo pipefail case "{{MODE}}" in build) docker build -t kei:dev . ;; multiarch) # The default `docker` driver can't build multiple platforms; # bootstrap (or reuse) a `docker-container` driver builder # named `kei-multiarch` for this invocation only. if ! docker buildx inspect kei-multiarch >/dev/null 2>&1; then docker buildx create --name kei-multiarch --driver docker-container >/dev/null fi docker buildx build --builder kei-multiarch \ --platform linux/amd64,linux/arm64 \ -t kei:dev . ;; run) docker compose up ;; shell) docker run --rm -it --entrypoint bash kei:dev ;; health) container=$(docker ps --filter ancestor=kei:dev --quiet | head -1) if [ -z "$container" ]; then echo "No running kei:dev container found." >&2 exit 1 fi docker exec "$container" cat /config/health.json ;; *) echo "Unknown mode: {{MODE}}" >&2 echo "Modes: build | multiarch | run | shell | health" >&2 exit 1 ;; esac # Reproduce release.yml's build + archive locally for TARGET (default host). release TARGET="": #!/usr/bin/env bash set -euo pipefail target="{{TARGET}}" if [ -z "$target" ]; then target=$(rustc -vV | awk '/^host:/ {print $2}') fi case "$target" in aarch64-unknown-linux-gnu) export CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc export CXX_aarch64_unknown_linux_gnu=aarch64-linux-gnu-g++ export CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_CXX=aarch64-linux-gnu-g++ export PKG_CONFIG_ALLOW_CROSS=1 export PKG_CONFIG_PATH=/usr/lib/aarch64-linux-gnu/pkgconfig ;; esac cargo build --locked --release --target "$target" mkdir -p dist case "$target" in *-windows-*) archive="dist/kei-$target.zip" (cd "target/$target/release" && zip "../../../$archive" kei.exe) ;; *) archive="dist/kei-$target.tar.gz" tar -C "target/$target/release" -czf "$archive" kei ;; esac checksum_file="dist/SHA256SUMS.txt" archive_name=$(basename "$archive") checksum=$(cd dist && sha256sum "$archive_name") tmp=$(mktemp "dist/SHA256SUMS.XXXXXX") if [ -f "$checksum_file" ]; then awk -v name="$archive_name" '$2 != name { print }' "$checksum_file" > "$tmp" fi printf '%s\n' "$checksum" >> "$tmp" mv "$tmp" "$checksum_file" echo "" echo "Archive: $archive" echo "Checksum written to dist/SHA256SUMS.txt" echo "" version=$(awk -F'"' '/^version = "/ {print $2; exit}' Cargo.toml) echo "=== CHANGELOG [$version] ===" awk -v ver="$version" ' /^## \[/ { in_section = ($0 ~ "^## \\[" ver "\\]"); next } in_section { print } ' CHANGELOG.md | sed '/./,$!d' | awk 'NR==1 && /^$/ {next} {print}' # Fuzz: list | build | run TARGET [SECONDS] | all [SECONDS]. Requires nightly + cargo-fuzz; install with `rustup install nightly && cargo install cargo-fuzz`. fuzz MODE="list" *ARGS="": #!/usr/bin/env bash set -euo pipefail if ! command -v cargo-fuzz >/dev/null 2>&1; then echo "cargo-fuzz not installed. Run: cargo install cargo-fuzz" >&2 exit 1 fi if ! rustup toolchain list | grep -q '^nightly'; then echo "nightly toolchain not installed. Run: rustup install nightly" >&2 exit 1 fi case "{{MODE}}" in list) cargo +nightly fuzz list ;; build) cargo +nightly fuzz build ;; all) args=({{ARGS}}) seconds="${args[0]:-60}" mapfile -t targets < <(cargo +nightly fuzz list) if [ "${#targets[@]}" -eq 0 ]; then echo "no fuzz targets found" >&2 exit 1 fi for target in "${targets[@]}"; do echo "" echo "=== fuzz $target (${seconds}s) ===" mkdir -p "fuzz/corpus/$target" extra=() if [ -d "fuzz/seeds/$target" ]; then extra+=("fuzz/seeds/$target") fi cargo +nightly fuzz run "$target" "fuzz/corpus/$target" "${extra[@]}" -- -max_total_time="$seconds" done ;; run) args=({{ARGS}}) target="${args[0]:-}" seconds="${args[1]:-60}" if [ -z "$target" ]; then echo "usage: just fuzz run TARGET [SECONDS]" >&2 cargo +nightly fuzz list exit 2 fi # libfuzzer treats the FIRST corpus dir as input/output (it # writes new coverage-improving inputs there) and any later dirs # as read-only auxiliary corpora. Pass fuzz/corpus/ # first so libfuzzer's auto-saved finds land there, and # fuzz/seeds/ second so checked-in regression inputs # replay every run without getting clobbered by autogen entries. mkdir -p "fuzz/corpus/$target" extra=() if [ -d "fuzz/seeds/$target" ]; then extra+=("fuzz/seeds/$target") fi cargo +nightly fuzz run "$target" "fuzz/corpus/$target" "${extra[@]}" -- -max_total_time="$seconds" ;; *) echo "Unknown mode: {{MODE}}" >&2 echo "Modes: list | build | run TARGET [SECONDS] | all [SECONDS]" >&2 exit 1 ;; esac # Check local tools needed by gate/full-test/cov/fuzz/docker recipes. doctor: #!/usr/bin/env bash set -euo pipefail status=0 ok() { printf '[ok] %s\n' "$1"; } fail() { printf '[miss] %s\n' "$1"; status=1; } note() { printf '[note] %s\n' "$1"; } have_cmd() { local cmd="$1" local label="${2:-$1}" local version="" if command -v "$cmd" >/dev/null 2>&1; then version=$("$cmd" --version 2>/dev/null | head -1 || true) ok "$label${version:+: $version}" else fail "$label not found" fi } have_cargo_subcommand() { local subcmd="$1" local label="${2:-cargo $subcmd}" local version="" if cargo "$subcmd" --version >/dev/null 2>&1; then version=$(cargo "$subcmd" --version 2>/dev/null | head -1 || true) ok "$label${version:+: $version}" else fail "$label not available" fi } have_cmd just have_cmd cargo have_cmd rustup if cargo +nightly --version >/dev/null 2>&1; then ok "nightly toolchain: $(cargo +nightly --version | head -1)" else fail "nightly toolchain not installed" fi have_cmd cargo-fuzz have_cargo_subcommand udeps "cargo-udeps" have_cargo_subcommand audit "cargo-audit" have_cargo_subcommand llvm-cov "cargo-llvm-cov" have_cmd typos have_cmd docker if docker buildx version >/dev/null 2>&1; then ok "docker buildx: $(docker buildx version | head -1)" else fail "docker buildx not available" fi if command -v systemd-analyze >/dev/null 2>&1 || command -v plutil >/dev/null 2>&1; then ok "service-smoke verifier available" else note "service-smoke verifier not found; service-smoke is Linux/macOS only" fi if [ -f .env ]; then ok ".env present for live recipes" else note ".env missing; live recipes need ICLOUD_USERNAME/ICLOUD_PASSWORD in environment" fi if [ -d .test-cookies ]; then ok ".test-cookies present" else note ".test-cookies missing; live auth may need a fresh login" fi exit "$status" # Service-install smoke: builds release, dry-run install, validates artifact, uninstall, asserts clean. # Mirrors .github/workflows/service-smoke.yml. Linux + macOS only. service-smoke: #!/usr/bin/env bash set -euxo pipefail cargo build --release KEI="$(scripts/full-test/cargo_target_dir.sh)/release/kei" # `kei status` derives its state-DB path from the username; pin a # placeholder so the smoke does not depend on the operator's .env # or saved config. export ICLOUD_USERNAME="service-smoke@example.invalid" assert_service_not_installed() { local status status=$("$KEI" status) printf '%s\n' "$status" | grep -q '^Service: not installed' } case "$(uname -s)" in Linux) UNIT="$HOME/.config/systemd/user/kei.service" UNIT_PREVIEW="${RUNNER_TEMP:-${TMPDIR:-/tmp}}/kei.service" # Clean any leftover from a previous failed run so the # pre-state assertion is meaningful. rm -f "$UNIT" "$UNIT_PREVIEW" test ! -e "$UNIT" assert_service_not_installed "$KEI" install --user --dry-run > "$UNIT_PREVIEW" test ! -e "$UNIT" systemd-analyze --user verify "$UNIT_PREVIEW" "$KEI" uninstall test ! -e "$UNIT" assert_service_not_installed ;; Darwin) PLIST="$HOME/Library/LaunchAgents/com.rhoopr.kei.plist" PLIST_PREVIEW="${RUNNER_TEMP:-${TMPDIR:-/tmp}}/com.rhoopr.kei.plist" rm -f "$PLIST" "$PLIST_PREVIEW" test ! -e "$PLIST" assert_service_not_installed "$KEI" install --dry-run > "$PLIST_PREVIEW" test ! -e "$PLIST" plutil -lint "$PLIST_PREVIEW" "$KEI" uninstall test ! -e "$PLIST" assert_service_not_installed ;; *) echo "service-smoke is Linux/macOS only; Windows runs in CI" >&2 exit 2 ;; esac # Disabled: not part of the active workflow. # Create branch NAME off a freshly fetched origin/main (AGENTS.md branch-from-fresh-main rule). # branch NAME: # #!/usr/bin/env bash # set -euo pipefail # git fetch origin main # if git show-ref --verify --quiet "refs/heads/{{NAME}}"; then # echo "branch '{{NAME}}' already exists locally" >&2 # exit 1 # fi # git switch -c "{{NAME}}" origin/main