Skip to content

feat(cli): consolidate maintenance surface under wright update #790

feat(cli): consolidate maintenance surface under wright update

feat(cli): consolidate maintenance surface under wright update #790

Workflow file for this run

name: CI
# ---------------------------------------------------------------------------
# Trigger rules
# ---------------------------------------------------------------------------
# Push to main: capability checks are selected by changed paths, just like PRs.
# Broad changes still run the full convergence set.
# PR: capability checks are selected by changed paths. Broad/core changes
# (Cargo workspace, scripts, test inputs, CI itself) expand to the
# full check set; narrow path changes run only the relevant capability.
# Docs-only changes skip CI entirely.
on:
push:
branches: ["main"]
paths-ignore:
- "**/*.md"
- "docs/**"
- "!benchmarks/**/*.md"
pull_request:
paths-ignore:
- "**/*.md"
- "docs/**"
- "!benchmarks/**/*.md"
permissions:
contents: read
# ---------------------------------------------------------------------------
# Path filter: determines which capability jobs run on PRs and pushes.
# ---------------------------------------------------------------------------
jobs:
paths:
name: Detect paths
runs-on: ubuntu-latest
outputs:
rust_core: ${{ steps.filter.outputs.rust_core }}
opy: ${{ steps.filter.outputs.rust_core }}
lpp: ${{ steps.filter.outputs.rust_core }}
cli: ${{ steps.filter.outputs.rust_core }}
dist: ${{ steps.filter.outputs.dist }}
steps:
- name: Checkout
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Filter paths
id: filter
uses: dorny/paths-filter@v4
with:
filters: |
rust_core:
- 'Cargo.toml'
- 'Cargo.lock'
- 'rust-toolchain'
- 'rust-toolchain.toml'
- '.cargo/**'
- 'src/**'
- 'crates/**'
- 'tests/fixtures/**'
- 'scripts/**'
- 'benchmarks/**'
- '.github/workflows/**'
dist:
- 'dist/**'
- 'install.sh'
- 'install.ps1'
- 'Cargo.toml'
- 'Cargo.lock'
- 'rust-toolchain'
- 'rust-toolchain.toml'
- '.cargo/**'
- 'src/**'
- 'crates/**'
- 'tests/fixtures/**'
- 'scripts/**'
- '.github/workflows/**'
# -------------------------------------------------------------------------
# [1] RUST QUALITY & TESTS (fundamental gate)
# All other Rust-based jobs depend on this.
# -------------------------------------------------------------------------
rust-quality:
name: Rust (stable)
needs: paths
if: needs.paths.outputs.rust_core == 'true'
uses: wrightkit/.github/.github/workflows/rust-quality.yml@a83d0f1b4cbcb5b81fb0426e3a5aea1db0899fb5
with:
toolchain: stable
cache-shared-key: linux-quality-stable-dev-test
all-features: true
# The reusable quality workflow installs its requested toolchain, but the
# repository rust-toolchain.toml remains authoritative for bare Cargo
# commands. Keep the MSRV gate local so every command explicitly selects
# 1.85.0 and cannot silently fall back to stable.
rust-msrv:
name: Rust (1.85.0)
needs: paths
if: needs.paths.outputs.rust_core == 'true'
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Install Rust (1.85.0)
uses: dtolnay/rust-toolchain@master
with:
toolchain: 1.85.0
components: rustfmt, clippy
- name: Rust cache
uses: Swatinem/rust-cache@v2
with:
shared-key: linux-quality-1.85.0-dev-test
cache-targets: true
cache-all-crates: false
cache-workspace-crates: false
cache-bin: false
save-if: ${{ github.ref == 'refs/heads/main' }}
cache-on-failure: false
- name: Check formatting
run: cargo +1.85.0 fmt --all -- --check
# Keep the surviving owner-independent Wright crates on the declared
# MSRV. The provider-backed consumer crates are covered by the stable
# full-workspace gate and the dedicated integration jobs below.
- name: Clippy (independent crates)
run: >-
cargo +1.85.0 clippy --locked --all-targets --all-features
-p wright-analyzer -p wright-transform -p wright-lpp -- -D warnings
- name: Test (independent crates)
run: >-
cargo +1.85.0 test --locked --all-targets --all-features
-p wright-analyzer -p wright-transform -p wright-lpp
# -------------------------------------------------------------------------
# [1a] WRIGHT CLI ARTIFACT (stable/debug executable for compatible jobs)
# Build this exact workflow revision once. Consumers below need the CLI
# executable only; jobs with another output or test-build identity stay
# independent.
# -------------------------------------------------------------------------
wright-cli-build:
name: Build CLI
needs: [paths, rust-quality, rust-msrv]
if: needs.paths.outputs.opy == 'true' || needs.paths.outputs.cli == 'true'
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Install Rust
uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
- name: Rust cache
uses: Swatinem/rust-cache@v2
with:
shared-key: linux-quality-stable-dev-test
cache-targets: true
cache-all-crates: false
cache-workspace-crates: false
cache-bin: false
save-if: false
cache-on-failure: false
- name: Build CLI
run: cargo build --locked -p wright-cli
- name: Upload CLI artifact
uses: actions/upload-artifact@v7
with:
name: wright-cli-linux-stable-debug
path: target/debug/wright
if-no-files-found: error
- name: Record CLI provenance
shell: bash
run: |
digest="$(sha256sum target/debug/wright | cut -d ' ' -f1)"
{
printf '%s\n\n' '## Wright CLI artifact'
printf -- "- Revision: \`%s\`\n" "$GITHUB_SHA"
printf '%s\n' '- Build identity: stable / debug / ubuntu-latest'
printf -- "- SHA-256: \`%s\`\n" "$digest"
} >> "$GITHUB_STEP_SUMMARY"
# -------------------------------------------------------------------------
# [1b] WORKSHOP REAL-PROJECT INTEGRATION (Wright's released Workshop consumer)
# Runs the owner-pinned real-project inputs through the actual `wright`
# check/lint commands. The source snapshots and residual expectations remain
# owned by the released workshop-rs corpus.
# -------------------------------------------------------------------------
workshop-integration:
name: Workshop (${{ matrix.project }})
needs: [paths, rust-quality, rust-msrv, wright-cli-build]
if: needs.paths.outputs.rust_core == 'true'
runs-on: ubuntu-latest
strategy:
fail-fast: true
matrix:
include:
- project: ai-pve
expected_exit: 0
- project: bastion
expected_exit: 0
# The pinned owner corpus admits this project's residual as a
# source-level failure; the other projects must remain clean.
- project: defend
expected_exit: 1
- project: illari
expected_exit: 0
- project: rework
expected_exit: 0
steps:
- name: Checkout Wright
uses: actions/checkout@v7
- name: Checkout workshop-rs
uses: actions/checkout@v7
with:
repository: wrightkit/workshop-rs
ref: f1f3c58f790e9aede9300ca6421698f2503f5f62 # v0.3.11
path: workshop-rs-pinned
- name: Download CLI artifact
uses: actions/download-artifact@v8
with:
name: wright-cli-linux-stable-debug
path: target/debug
- name: Prepare CLI executable
run: chmod +x target/debug/wright
- name: Run public CLI dogfood
env:
EXPECTED_EXIT: ${{ matrix.expected_exit }}
PROJECT: workshop-rs-pinned/crates/workshop-rs/tests/fixtures/real-projects/${{ matrix.project }}.ow
run: |
set -euo pipefail
run_workflow() {
local command="$1"
local output
local status
set +e
output="$(target/debug/wright "$command" --kind workshop "$PROJECT" --format json)"
status=$?
set -e
if [[ "$status" -ne "$EXPECTED_EXIT" ]]; then
echo "wright $command returned $status; expected $EXPECTED_EXIT for $PROJECT" >&2
return 1
fi
jq -e \
--arg command "$command" \
--argjson process_exit "$status" \
'(
.wright.contract == "wright-result/v1" and
.command == $command and
.exit == $process_exit and
.ok == ($process_exit == 0) and
(.diagnostics | type == "array") and
(all(.diagnostics[]; type == "object")) and
([.diagnostics[] | select(.stage == "internal")] | length == 0)
)' <<<"$output" >/dev/null
printf '%s' "$output"
}
check_output="$(run_workflow check)"
lint_output="$(run_workflow lint)"
jq -e -n \
--argjson check "$check_output" \
--argjson lint "$lint_output" \
'(
$check.diagnostics == $lint.diagnostics and
$check.ok == $lint.ok and
$check.exit == $lint.exit and
($lint.result.program.rules | numbers | . > 0)
)' >/dev/null
# -------------------------------------------------------------------------
# [2] PRODUCT INTEGRATION (Wright-owned provider and product contracts)
# The source-language owners test language semantics. Wright tests its
# provider seam, provider distribution success path, failure routing, and
# user-facing product behavior directly.
# -------------------------------------------------------------------------
opy-integration:
name: Product integration
needs: [paths, rust-quality, rust-msrv, wright-cli-build]
if: needs.paths.outputs.opy == 'true'
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Install Rust
uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
- name: Download CLI artifact
uses: actions/download-artifact@v8
with:
name: wright-cli-linux-stable-debug
path: target/debug
- name: Prepare CLI executable
run: chmod +x target/debug/wright
- name: Rust cache
uses: Swatinem/rust-cache@v2
with:
shared-key: linux-quality-stable-dev-test
cache-targets: true
cache-all-crates: false
cache-workspace-crates: false
cache-bin: false
save-if: false
cache-on-failure: false
- name: Run provider/product tests
run: >-
cargo test --locked
-p wright-driver --test driver --test source_provider --test service
--test workshop_provider
- name: Run CLI/embedding tests
run: >-
cargo test --locked
-p wright-cli --test cli --test diagnostics_schema --test serve
&& cargo test --locked -p wright-consumer --test consumer
- name: Install pinned OPY provider
run: target/debug/wright update provider opy --version 0.1.38
- name: Compile OPY via provider
run: >-
target/debug/wright compile tests/fixtures/opy/basic-rule.opy
--profile compat
# -------------------------------------------------------------------------
# [3] LPP INTEGRATION (Wright's Language Provider Protocol client contract)
# -------------------------------------------------------------------------
lpp-integration:
name: LPP integration
needs: [paths, rust-quality, rust-msrv]
if: needs.paths.outputs.lpp == 'true'
runs-on: ubuntu-latest
steps:
- name: Checkout Wright
uses: actions/checkout@v7
- name: Install Rust
uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
- name: Rust cache
uses: Swatinem/rust-cache@v2
with:
shared-key: linux-quality-stable-dev-test
cache-targets: true
cache-all-crates: false
cache-workspace-crates: false
cache-bin: false
save-if: false
cache-on-failure: false
# The mock provider is built from the pinned language-provider-protocol
# commit the integration tests were validated against (merged LPP v1,
# PR wrightkit/language-provider-protocol#2 plus docs-only follow-ups).
- name: Checkout LPP
uses: actions/checkout@v7
with:
repository: wrightkit/language-provider-protocol
ref: 416b293e26e6fb2d29061608a493a7aecd2ce14f
path: language-provider-protocol
- name: Build mock provider
run: cargo build --locked -p lpp-mock-provider
working-directory: language-provider-protocol
# REQUIRED: these suites self-skip when LPP_MOCK_PROVIDER is absent,
# so the variable must be set here for them to run at all.
- name: Run LPP tests
env:
LPP_MOCK_PROVIDER: ${{ github.workspace }}/language-provider-protocol/target/debug/lpp-mock-provider
run: >-
cargo test --locked -p wright-lpp -p wright-driver
--test mock_provider --test lpp --test provider_edit
# -------------------------------------------------------------------------
# [4] CLI BEHAVIOR (Wright CLI GitHub Actions renderer smoke test)
# Validates CLI output format in a real GitHub Actions environment.
# Related to #164 (user-facing GHA renderer); this job validates the
# repository CI integration, not the renderer semantics layer.
# -------------------------------------------------------------------------
cli-behavior:
name: CLI behavior
needs: [paths, rust-quality, rust-msrv, wright-cli-build]
if: needs.paths.outputs.cli == 'true'
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Install Rust
uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
- name: Download CLI artifact
uses: actions/download-artifact@v8
with:
name: wright-cli-linux-stable-debug
path: target/debug
- name: Prepare CLI executable
run: chmod +x target/debug/wright
- name: Test GitHub Actions renderer
run: scripts/test-cli-github-actions.sh target/debug/wright
# -------------------------------------------------------------------------
# [5] BENCHMARK (performance evidence)
# Non-blocking on PRs; records regression-detectable output as artifact.
# Depends on rust-quality so a fundamental failure skips benchmark time.
# -------------------------------------------------------------------------
benchmark:
name: Benchmarks
needs: [paths, rust-quality, rust-msrv]
if: needs.paths.outputs.opy == 'true'
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Install Rust
uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
- name: Rust cache
uses: Swatinem/rust-cache@v2
with:
shared-key: linux-quality-stable-dev-test
cache-targets: true
cache-all-crates: false
cache-workspace-crates: false
cache-bin: false
save-if: false
cache-on-failure: false
- name: Build benchmarks
run: cargo build --locked -p wright-bench
- name: Run benchmarks
id: bench
run: target/debug/wright-bench
- name: Validate agent benchmark scenarios
run: |
cargo build --locked -p wright-cli
python3 benchmarks/agent/agent_bench.py validate
python3 -m unittest discover -s benchmarks/agent
- name: Upload benchmark report
if: always()
uses: actions/upload-artifact@v7
with:
name: benchmark-report
path: target/wright-bench-report.json
# -------------------------------------------------------------------------
# [8] DISTRIBUTION VALIDATION (cross-platform package/binary smoke)
# When source changes are in scope, this remains gated on the relevant Rust
# and integration jobs. Distribution-only changes run this job directly.
# -------------------------------------------------------------------------
dist-cli-build:
name: Build dist (${{ matrix.os }})
needs:
- paths
- rust-quality
- rust-msrv
- opy-integration
- lpp-integration
if: |
always() &&
needs.paths.outputs.dist == 'true' &&
(needs.rust-quality.result == 'success' || needs.paths.outputs.rust_core != 'true') &&
(needs.rust-msrv.result == 'success' || needs.paths.outputs.rust_core != 'true') &&
(needs.opy-integration.result == 'success' || needs.paths.outputs.opy != 'true') &&
(needs.lpp-integration.result == 'success' || needs.paths.outputs.lpp != 'true')
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os:
- ubuntu-latest
- macos-15
- windows-latest
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Install Rust
uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
- name: Rust cache
uses: Swatinem/rust-cache@v2
with:
shared-key: dist-validation-${{ runner.os }}-stable-dev
cache-targets: false
cache-all-crates: false
cache-workspace-crates: false
cache-bin: false
save-if: ${{ github.ref == 'refs/heads/main' }}
cache-on-failure: false
- name: Build CLI and LSP
shell: bash
run: cargo build --locked -p wright-cli -p wright-lsp
- name: Upload distribution binaries
uses: actions/upload-artifact@v7
with:
name: wright-dist-cli-${{ matrix.os }}
path: |
target/debug/wright
target/debug/wright.exe
target/debug/wright-lsp
target/debug/wright-lsp.exe
if-no-files-found: error
dist-validation:
name: Dist validation (${{ matrix.channel }}, ${{ matrix.os }})
needs:
- paths
- rust-quality
- rust-msrv
- opy-integration
- lpp-integration
- dist-cli-build
if: |
always() &&
needs.paths.outputs.dist == 'true' &&
(needs.rust-quality.result == 'success' || needs.paths.outputs.rust_core != 'true') &&
(needs.rust-msrv.result == 'success' || needs.paths.outputs.rust_core != 'true') &&
(needs.opy-integration.result == 'success' || needs.paths.outputs.opy != 'true') &&
(needs.lpp-integration.result == 'success' || needs.paths.outputs.lpp != 'true') &&
needs.dist-cli-build.result == 'success'
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
channel: install.sh
- os: macos-15
channel: install.sh
- os: macos-15
channel: Homebrew
- os: windows-latest
channel: install.ps1
- os: windows-latest
channel: Scoop
- os: windows-latest
channel: WinGet
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Set up Python
uses: actions/setup-python@v7
with:
python-version: "3.12"
- name: Install Scoop
if: matrix.channel == 'Scoop'
shell: pwsh
run: ./scripts/setup-scoop.ps1
- name: Enable WinGet local manifests
if: matrix.channel == 'WinGet'
shell: pwsh
run: winget settings --enable LocalManifestFiles
- name: Download distribution binaries
uses: actions/download-artifact@v8
with:
name: wright-dist-cli-${{ matrix.os }}
path: target/debug
- name: Prepare distribution executables
if: runner.os != 'Windows'
run: chmod +x target/debug/wright target/debug/wright-lsp
- name: Validate package metadata and installers
if: matrix.channel == 'install.sh' || matrix.channel == 'install.ps1'
run: python scripts/verify-dist.py
- name: Test install.sh (functional)
if: matrix.channel == 'install.sh'
run: scripts/test-install.sh
- name: Test install.sh (native smoke)
if: matrix.channel == 'install.sh'
run: python scripts/test-distribution-install-sh.py
- name: Test install.ps1 (channel)
if: matrix.channel == 'install.ps1'
shell: pwsh
run: ./scripts/test-install.ps1
- name: Test install.ps1 (R2 smoke)
if: matrix.channel == 'install.ps1'
shell: pwsh
run: ./scripts/test-install-r2.ps1
- name: Test Homebrew channel
if: matrix.channel == 'Homebrew'
run: python scripts/test-distribution-homebrew.py
- name: Test Scoop channel
if: matrix.channel == 'Scoop'
run: python scripts/test-distribution-scoop.py
- name: Test WinGet channel
if: matrix.channel == 'WinGet'
run: python scripts/test-distribution-winget.py