wavepeek 2.1.0

Command-line tool for RTL waveform inspection with deterministic machine-friendly output.
Documentation
set shell := ["bash", "-euo", "pipefail", "-c"]

export RTL_ARTIFACTS_DIR := `. ./.devcontainer/env_contract.sh; printf '%s\n' "$RTL_ARTIFACTS_DIR"`
schema_check_dir := "tmp/schema-check"
bench_e2e_fsdb_tests := "bench/e2e/tests_fsdb.json"
bench_e2e_fsdb_smoke_filter := "^(info_picorv32_ez|scope_scr1_all_depth7_json|signal_scr1_top_recursive_depth2_json|value_scr1_signals_1|change_scr1_signals_1_window_2ns_trigger_any)$"
bench_e2e_fsdb_smoke_artifact_filter := "^(picorv32_test_ez_vcd|scr1_max_axi_riscv_compliance)[.]fst$"
wavepeek_release_bin := "./target/release/wavepeek"
wavepeek_fsdb_release_bin := "./target/fsdb/release/wavepeek"
codex_setup_script := "tools/codex/codex_setup.sh"
codex_resume_script := "tools/codex/codex_resume.sh"
python := "python3 -B"
docs_site_dir := "tmp/docs-site"
docs_pages_url := "https://kleverhq.github.io/wavepeek"
docs_repository := env_var_or_default("DOCS_REPOSITORY", "")
docs_version := `python3 -B -c 'import pathlib, tomllib; print(tomllib.loads(pathlib.Path("Cargo.toml").read_text(encoding="utf-8"))["package"]["version"])'`
coverage_src_threshold := env_var_or_default("COVERAGE_SRC_THRESHOLD", "90")

[private]
default: help

[private]
print-coverage-src-threshold:
    @printf '%s\n' "{{ coverage_src_threshold }}"

[private]
require-container:
    @if [ "${WAVEPEEK_IN_CONTAINER:-0}" != "1" ]; then \
        printf '%s\n' "error: container: this target must run inside a wavepeek-managed container environment (set WAVEPEEK_IN_CONTAINER=1)" >&2; \
        exit 1; \
    fi

[private]
require-verdi: require-container
    @{{ python }} tools/fsdb/check_fsdb_env.py --require >/dev/null

[private]
run-if-verdi recipe: require-container
    @set +e; \
    output="$({{ python }} tools/fsdb/check_fsdb_env.py 2>&1)"; \
    status="$?"; \
    set -e; \
    if [ "$status" -eq 0 ]; then \
        printf '%s\n' "$output"; \
        just "{{ recipe }}"; \
    elif [ "$status" -eq 77 ]; then \
        printf '%s\n' "$output"; \
    else \
        printf '%s\n' "$output" >&2; \
        exit "$status"; \
    fi

[private]
check-rtl-artifacts: require-container
    @. ./.devcontainer/env_contract.sh; \
    for fixture in $WAVEPEEK_RTL_ARTIFACT_FILES; do \
        if [ ! -f "${RTL_ARTIFACTS_DIR}/$fixture" ]; then \
            printf '%s\n' "error: file: required fixture missing at ${RTL_ARTIFACTS_DIR}/$fixture" >&2; \
            exit 1; \
        fi; \
    done

# Regenerate canonical schema artifacts from Rust contract code
update-schema: require-container
    cargo run --quiet --manifest-path tools/schema-gen/Cargo.toml -- --out schema

# Validate canonical schema freshness and JSON contract URL
check-schema: require-container
    @rm -rf "{{ schema_check_dir }}"
    cargo run --quiet --manifest-path tools/schema-gen/Cargo.toml -- --out "{{ schema_check_dir }}"
    @{{ python }} tools/schema/check_schema_contract.py --schema-dir schema --generated-dir "{{ schema_check_dir }}"

# Lint GitHub Actions workflows
check-actions: require-container
    actionlint .github/workflows/*.yml

# Regenerate FSDB benchmark catalog from the FST benchmark catalog
update-bench-e2e-fsdb-catalog: require-container
    @{{ python }} tools/fsdb/generate_bench_catalog.py

# Validate FSDB benchmark catalog freshness
check-bench-e2e-fsdb-catalog: require-container
    @{{ python }} tools/fsdb/generate_bench_catalog.py --check

# Prepare local devcontainer environment and install git hooks
dev-setup: require-container
    rustup show >/dev/null
    cargo --version
    cargo fmt --version
    cargo clippy --version
    actionlint -version
    devcontainer --version
    gtkwave --version
    surfer --version
    mkdocs --version
    mike --version
    just --version
    pre-commit install --hook-type commit-msg --hook-type pre-commit

# Prepare Codex cloud environment for non-dev just recipes
codex-setup: require-container
    bash "{{ codex_setup_script }}"

# Repair Codex cloud environment after cache resume
codex-resume: require-container
    bash "{{ codex_resume_script }}"

# Format root justfile in place
format-justfile: require-container
    @just --unstable --fmt

# Check root justfile formatting
format-justfile-check: require-container
    @just --unstable --fmt --check

# Format with rustfmt and justfile formatter
format: require-container
    cargo fmt
    just format-justfile

# Check formatting with rustfmt and justfile formatter
format-check: require-container
    cargo fmt -- --check
    just format-justfile-check

# Lint with clippy
lint: require-container
    cargo clippy --all-targets -- -D warnings
    just run-if-verdi lint-fsdb

# Fix linting with clippy
lint-fix: require-container
    cargo clippy --all-targets --fix --allow-dirty --allow-staged -- -D warnings

# Type check with cargo
check-build: require-container
    cargo check

# Run tests with cargo
test: require-container check-rtl-artifacts
    cargo test -q
    just run-if-verdi test-fsdb

[private]
coverage-src-data: require-container check-rtl-artifacts
    @mkdir -p tmp/coverage
    cargo llvm-cov --workspace --summary-only --json --ignore-filename-regex '(/tests/|/target/|/\.cargo/registry/|/rustc/)' > tmp/coverage/coverage-src-summary.json

# Report source coverage for src/**/*.rs via cargo-llvm-cov
coverage-src: coverage-src-data
    {{ python }} tools/coverage/check_coverage.py \
        --summary-json tmp/coverage/coverage-src-summary.json \
        --min-regions 0 \
        --min-functions 0 \
        --min-lines 0

# Enforce minimum source coverage for src/**/*.rs
coverage-src-check: coverage-src-data
    {{ python }} tools/coverage/check_coverage.py \
        --summary-json tmp/coverage/coverage-src-summary.json \
        --min-regions {{ coverage_src_threshold }} \
        --min-functions {{ coverage_src_threshold }} \
        --min-lines {{ coverage_src_threshold }} \
        --markdown-output tmp/coverage/coverage-src-summary.md

# Check local FSDB Reader SDK availability
check-fsdb-env: require-container
    @set +e; \
    {{ python }} tools/fsdb/check_fsdb_env.py; \
    status="$?"; \
    if [ "$status" -eq 77 ]; then \
        exit 0; \
    fi; \
    exit "$status"

# Lint optional FSDB support
lint-fsdb: require-verdi
    CARGO_TARGET_DIR=target/fsdb cargo clippy --features fsdb --all-targets -- -D warnings

# Prepare generated FSDB fixtures from VCD fixtures and RTL FST artifacts
prepare-fsdb-fixtures: require-verdi check-bench-e2e-fsdb-catalog
    bash tools/fsdb/prepare_fsdb_fixtures.sh

# Prepare generated FSDB fixtures from hand-written VCD test fixtures only
prepare-fsdb-test-fixtures: require-verdi
    bash tools/fsdb/prepare_fsdb_fixtures.sh --hand-only

# Verify FSDB benchmark artifacts exist next to required RTL FST fixtures
check-fsdb-rtl-artifacts: require-verdi check-rtl-artifacts
    {{ python }} tools/fsdb/check_fsdb_bench_artifacts.py "{{ bench_e2e_fsdb_tests }}"

# Prepare and verify FSDB benchmark artifacts in dependency order
prepare-and-check-fsdb-rtl-artifacts: require-verdi
    just check-rtl-artifacts
    just prepare-fsdb-fixtures
    {{ python }} tools/fsdb/check_fsdb_bench_artifacts.py "{{ bench_e2e_fsdb_tests }}"

# Prepare and verify only FSDB RTL artifacts required by the pre-commit smoke
prepare-and-check-fsdb-smoke-rtl-artifacts: require-verdi
    just check-rtl-artifacts
    just check-bench-e2e-fsdb-catalog
    bash tools/fsdb/prepare_fsdb_fixtures.sh --rtl-only --rtl-filter '{{ bench_e2e_fsdb_smoke_artifact_filter }}'
    {{ python }} tools/fsdb/check_fsdb_bench_artifacts.py "{{ bench_e2e_fsdb_tests }}" --filter '{{ bench_e2e_fsdb_smoke_filter }}'

# Build release binary with optional FSDB support
build-release-fsdb: require-verdi
    CARGO_TARGET_DIR=target/fsdb cargo build --release --features fsdb

# Build and smoke-test optional FSDB support
check-fsdb-build: require-verdi
    @fsdb_libdir="$({{ python }} tools/fsdb/check_fsdb_env.py --require --print-libdir)"; \
    export CARGO_TARGET_DIR=target/fsdb; \
    cargo check --features fsdb; \
    cargo build --features fsdb; \
    readelf_output="$(readelf -d target/fsdb/debug/wavepeek)"; \
    if printf '%s\n' "$readelf_output" | grep -Eq '\(NEEDED\).*Shared library: \[/'; then \
        printf '%s\n' "error: fsdb: built binary must not contain an absolute DT_NEEDED path" >&2; \
        exit 1; \
    fi; \
    if ! printf '%s\n' "$readelf_output" | grep -Eq '\(NEEDED\).*Shared library: \[libz\.so(\.[^]]*)?\]'; then \
        printf '%s\n' "error: fsdb: built binary must contain a libz DT_NEEDED entry" >&2; \
        exit 1; \
    fi; \
    if ! printf '%s\n' "$readelf_output" | grep -E '\((RPATH|RUNPATH)\)' | grep -F -- "$fsdb_libdir" >/dev/null; then \
        printf '%s\n' "error: fsdb: built binary must contain an ELF RPATH/RUNPATH for $fsdb_libdir" >&2; \
        exit 1; \
    fi; \
    cargo test --features fsdb --lib fsdb_reader_metadata_smoke -- --nocapture; \
    cargo test --features fsdb --lib fsdb_reader_hierarchy_smoke -- --nocapture

# Run optional FSDB build smoke tests
test-fsdb: check-fsdb-build prepare-fsdb-test-fixtures
    @export CARGO_TARGET_DIR=target/fsdb; \
    cargo test --features fsdb --lib fsdb_expr_event_occurred_rejects_non_event_signal -- --nocapture && \
    cargo test --features fsdb --test fsdb_cli

# Run auxiliary Python/unit test suites
test-aux: require-container
    @just check-bench-e2e-fsdb-catalog
    {{ python }} -m unittest discover -s bench/e2e -p "test_*.py"
    {{ python }} -m unittest discover -s tools/bench -p "test_*.py"
    {{ python }} -m unittest discover -s tools/docs -p "test_*.py"
    {{ python }} -m unittest discover -s tools/release -p "test_*.py"
    {{ python }} -m unittest discover -s tools/schema -p "test_*.py"
    {{ python }} -m unittest tools/coverage/test_check_coverage.py
    {{ python }} -m unittest discover -s tools/fsdb -p "test_*.py"
    {{ python }} -m unittest discover -s tools/repo -p "test_*.py"

# Build the generated MkDocs site from current embedded docs
docs-site-build: require-container
    cargo run --quiet -- docs export "{{ docs_site_dir }}/export" --force
    {{ python }} tools/docs/prepare_mkdocs.py "{{ docs_site_dir }}/export" \
        --output "{{ docs_site_dir }}/mkdocs-src" \
        --config-output "{{ docs_site_dir }}/mkdocs.yml" \
        --version "{{ docs_version }}" \
        --force
    mkdocs build --strict --config-file "{{ docs_site_dir }}/mkdocs.yml"

# Serve the generated docs site locally
docs-site-serve: docs-site-build
    mkdocs serve --config-file "{{ docs_site_dir }}/mkdocs.yml"

# Check docs site generation and root Pages artifacts without touching gh-pages
docs-site-check: require-container
    {{ python }} tools/docs/publish_docs.py check \
        --version "{{ docs_version }}" \
        --source-root . \
        --work-dir "{{ docs_site_dir }}"

# Stage a local gh-pages update for a release tag without pushing
docs-site-stage-deploy version=docs_version source_ref=("v" + docs_version) repair="0": require-container
    @mkdir -p "{{ docs_site_dir }}/release-assets"
    gh release download "{{ source_ref }}" --pattern wavepeek-installer.sh --pattern wavepeek-installer.ps1 --dir "{{ docs_site_dir }}/release-assets" --clobber
    @repair_arg=""; \
        if [ "{{ repair }}" = "1" ] || [ "{{ repair }}" = "true" ]; then \
            repair_arg="--repair-existing-version"; \
        fi; \
        {{ python }} tools/docs/publish_docs.py stage-deploy \
            --version "{{ version }}" \
            --source-ref "{{ source_ref }}" \
            --work-dir "{{ docs_site_dir }}" \
            $repair_arg

# Verify and push the staged gh-pages bundle
docs-site-push-staged version=docs_version repair="0": require-container
    @repair_arg=""; \
        if [ "{{ repair }}" = "1" ] || [ "{{ repair }}" = "true" ]; then \
            repair_arg="--repair-existing-version"; \
        fi; \
        {{ python }} tools/docs/publish_docs.py push-staged \
            --version "{{ version }}" \
            --work-dir "{{ docs_site_dir }}" \
            $repair_arg

# Stage and push a release docs update
docs-site-deploy version=docs_version source_ref=("v" + docs_version) repair="0": require-container
    just docs-site-stage-deploy "{{ version }}" "{{ source_ref }}" "{{ repair }}"
    just docs-site-push-staged "{{ version }}" "{{ repair }}"

# Manually dispatch the remote docs publication workflow
docs-site-dispatch version=docs_version source_ref=("v" + docs_version) repair="false" ref="main": require-container
    gh workflow run docs.yml \
        --ref "{{ ref }}" \
        -f version="{{ version }}" \
        -f source_ref="{{ source_ref }}" \
        -F repair_existing_version="{{ repair }}"

# Check deployed GitHub Pages docs for a release version
docs-site-check-deploy version=docs_version base_url=docs_pages_url repository=docs_repository: require-container
    @repo_arg=(); \
        if [ -n "{{ repository }}" ]; then \
            repo_arg=(--repository "{{ repository }}"); \
        fi; \
        schema_args=($({{ python }} -c 'import json, pathlib, tomllib, urllib.parse; requested="{{ version }}"; package=tomllib.loads(pathlib.Path("Cargo.toml").read_text(encoding="utf-8"))["package"]["version"]; p=pathlib.Path("schema/catalog.json"); c=json.loads(p.read_text(encoding="utf-8")) if requested == package and p.is_file() else {"families": []}; flags={"wavepeek.output":"--schema-artifact","wavepeek.stream-record":"--stream-schema-artifact","wavepeek.input":"--input-schema-artifact"}; [print(flags[e["id"]], pathlib.PurePosixPath(urllib.parse.urlparse(e["url"]).path).name) for e in c.get("families", []) if e.get("id") in flags]')); \
        {{ python }} tools/docs/check_deploy.py \
            --version "{{ version }}" \
            --base-url "{{ base_url }}" \
            "${schema_args[@]}" \
            "${repo_arg[@]}"

# Build release binary
build-release: require-container
    cargo build --release

# Run the manual performance gate for two source refs
bench-gate baseline_ref revised_ref="HEAD" fsdb="auto": require-container
    {{ python }} tools/bench/gate.py --baseline-ref "{{ baseline_ref }}" --revised-ref "{{ revised_ref }}" --fsdb "{{ fsdb }}"

# Capture benchmark artifacts for one source ref
bench-capture ref="HEAD" fsdb="auto": require-container
    {{ python }} tools/bench/capture.py --ref "{{ ref }}" --fsdb "{{ fsdb }}"

# Compare two benchmark capture directories
bench-compare golden_dir revised_dir: require-container
    {{ python }} tools/bench/compare.py --golden "{{ golden_dir }}" --revised "{{ revised_dir }}"

[private]
bench-e2e-run: check-rtl-artifacts build-release
    {{ python }} bench/e2e/perf.py run --binary subject="{{ wavepeek_release_bin }}"

[private]
bench-e2e-fsdb-run: prepare-and-check-fsdb-rtl-artifacts build-release-fsdb
    {{ python }} bench/e2e/perf.py run --binary subject="{{ wavepeek_fsdb_release_bin }}" --tests "{{ bench_e2e_fsdb_tests }}"

# Run lightweight benchmark e2e smoke for pre-commit
[private]
bench-e2e-smoke-commit: check-rtl-artifacts build-release
    @tmp_revised="$(mktemp -d)"; trap 'rm -rf "$tmp_revised"' EXIT; \
        {{ python }} bench/e2e/perf.py run --binary subject="{{ wavepeek_release_bin }}" --tests bench/e2e/tests_commit.json --run-dir "$tmp_revised"
    @just run-if-verdi bench-e2e-fsdb-smoke-commit

# Run lightweight FSDB benchmark e2e smoke for pre-commit
[private]
bench-e2e-fsdb-smoke-commit: prepare-and-check-fsdb-smoke-rtl-artifacts build-release-fsdb
    @tmp_revised="$(mktemp -d)"; trap 'rm -rf "$tmp_revised"' EXIT; \
        {{ python }} bench/e2e/perf.py run --binary subject="{{ wavepeek_fsdb_release_bin }}" --tests "{{ bench_e2e_fsdb_tests }}" --run-dir "$tmp_revised" --filter '{{ bench_e2e_fsdb_smoke_filter }}'

# Run pre-commit hooks on all files
pre-commit: require-container check-rtl-artifacts
    pre-commit run --all-files

# Check commit messages
check-commit: require-container
    cz check --commit-msg-file "$(git rev-parse --git-path COMMIT_EDITMSG)"

# Check everything
check: format-check lint check-schema check-actions check-bench-e2e-fsdb-catalog check-build docs-site-check check-commit
    @just run-if-verdi check-fsdb-build

# CI quality gate (no commit-msg hook)
ci: format-check lint check-schema check-actions test-aux coverage-src-check check-build docs-site-check
    @just run-if-verdi test-fsdb

# Fix everything
fix: format lint-fix update-schema

# Clean up
clean: require-container
    cargo clean

# Show recipes
help: require-container
    @just --list