name: 'spec-drift'
description: 'Semantic coherence analysis between specification and Rust implementation'
author: 'spec-drift contributors'
branding:
icon: 'file-text'
color: 'blue'
inputs:
version:
description: 'spec-drift ref. Accepts a tag (`v0.1.0`) for pre-built binaries, or a branch/SHA to build from source.'
required: false
default: 'v0.1.0'
format:
description: 'Output format: human, json, or sarif'
required: false
default: 'human'
output:
description: 'Write output to this path (stdout if empty)'
required: false
default: ''
deny:
description: 'Fail when any divergence at or above this severity exists: notice | warning | critical'
required: false
default: 'notice'
args:
description: 'Additional arguments forwarded to spec-drift'
required: false
default: ''
working-directory:
description: 'Working directory for the scan'
required: false
default: '.'
anthropic-api-key:
description: 'Anthropic API key for LLM-backed rules. Omit to run without LLM.'
required: false
default: ''
runs:
using: 'composite'
steps:
- name: Cache spec-drift binary
id: cache-spec-drift
uses: actions/cache@v4
with:
path: ~/.cargo/bin/spec-drift
key: spec-drift-${{ inputs.version }}-${{ runner.os }}-${{ runner.arch }}
- name: Download pre-built binary
id: download
if: steps.cache-spec-drift.outputs.cache-hit != 'true'
shell: bash
run: |
set -euo pipefail
# Only tagged versions have pre-built binaries.
case "${{ inputs.version }}" in
v*.*.*) ;;
*)
echo "version `${{ inputs.version }}` is not a semver tag; skipping binary download"
echo "downloaded=false" >> "$GITHUB_OUTPUT"
exit 0
;;
esac
case "${{ runner.os }}-${{ runner.arch }}" in
Linux-X64) TARGET=x86_64-unknown-linux-gnu ;;
macOS-ARM64) TARGET=aarch64-apple-darwin ;;
macOS-X64) TARGET=x86_64-apple-darwin ;;
*)
echo "no pre-built binary for ${{ runner.os }}-${{ runner.arch }}; will fall back to source install"
echo "downloaded=false" >> "$GITHUB_OUTPUT"
exit 0
;;
esac
URL="https://github.com/asmuelle/spec-drift/releases/download/${{ inputs.version }}/spec-drift-${TARGET}.tar.gz"
echo "Fetching ${URL}"
if curl -sfL -o /tmp/spec-drift.tar.gz "$URL"; then
mkdir -p "$HOME/.cargo/bin"
tar -xzf /tmp/spec-drift.tar.gz -C "$HOME/.cargo/bin"
chmod +x "$HOME/.cargo/bin/spec-drift"
echo "downloaded=true" >> "$GITHUB_OUTPUT"
else
echo "release asset not found; falling back to source install"
echo "downloaded=false" >> "$GITHUB_OUTPUT"
fi
- name: Install Rust toolchain (fallback)
if: steps.cache-spec-drift.outputs.cache-hit != 'true' && steps.download.outputs.downloaded != 'true'
uses: dtolnay/rust-toolchain@stable
with:
toolchain: "1.95"
- name: Install spec-drift from source (fallback)
if: steps.cache-spec-drift.outputs.cache-hit != 'true' && steps.download.outputs.downloaded != 'true'
shell: bash
run: |
set -euo pipefail
VERSION="${{ inputs.version }}"
INSTALL_REF=(--branch "$VERSION")
case "$VERSION" in
v*.*.*)
INSTALL_REF=(--tag "$VERSION")
;;
*)
if [[ "$VERSION" =~ ^[0-9a-fA-F]{7,40}$ ]]; then
INSTALL_REF=(--rev "$VERSION")
fi
;;
esac
cargo install --git https://github.com/asmuelle/spec-drift "${INSTALL_REF[@]}" --locked
- name: Add cargo bin to PATH
shell: bash
run: echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
- name: Run spec-drift
shell: bash
working-directory: ${{ inputs.working-directory }}
env:
ANTHROPIC_API_KEY: ${{ inputs.anthropic-api-key }}
run: |
set -o pipefail
ARGS="--format ${{ inputs.format }} --deny ${{ inputs.deny }} ${{ inputs.args }}"
if [ -n "${{ inputs.output }}" ]; then
# shellcheck disable=SC2086
spec-drift $ARGS > "${{ inputs.output }}"
else
# shellcheck disable=SC2086
spec-drift $ARGS
fi