Files
wehub-resource-sync 26382a7ac6
CI / Clippy (push) Failing after 15m13s
CI / Test (ubuntu-latest) (push) Failing after 16m1s
CI / Test (macos-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Build (no embeddings / no ORT) (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / Cookbook (Node) (push) Has been cancelled
CI / Pi Extension (Node) (push) Has been cancelled
CI / Rust SDK (lean-ctx-client) (push) Has been cancelled
CI / Embed SDK (lean-ctx-sdk) (push) Has been cancelled
CI / Python SDK (leanctx) (push) Has been cancelled
CI / Hermes Plugin (Python) (push) Has been cancelled
CI / SDK Conformance Matrix (push) Has been cancelled
CI / Coverage (push) Has been cancelled
CI / cargo-deny (push) Has been cancelled
CI / Adversarial Safety (push) Has been cancelled
CI / Benchmarks (push) Has been cancelled
CI / Output-Quality Gate (eval A/B) (push) Has been cancelled
CI / Documentation (push) Has been cancelled
CI / CI Green (push) Has been cancelled
JetBrains Plugin / Actionlint (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
CodeQL / Analyze (rust) (push) Has been cancelled
JetBrains Plugin / Validation (push) Has been cancelled
JetBrains Plugin / Build (push) Has been cancelled
JetBrains Plugin / Test (push) Has been cancelled
Security Check / Security Scan (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:35:30 +08:00

484 lines
19 KiB
YAML

name: Release
on:
push:
tags:
# Only true semver release tags (v1.2.3). The digit class deliberately
# excludes the editor-extension tags (`vscode-v*`), which have their own
# workflow (publish-vscode.yml) and must NOT trigger a binary release.
- 'v[0-9]*'
permissions:
contents: write
jobs:
sdk-gate:
# GL #395: an engine release must never ship while a first-party SDK
# cannot speak its http_mcp contract version. Minor-version drift across
# the SDK family surfaces as a warning annotation.
#
# Also gates the engine-coupled npm wrappers (pi-lean-ctx, lean-ctx-bin):
# a stale package.json would let the publish step silently skip, shipping a
# release whose Pi extension / npx wrapper lags the engine.
name: Release gates (SDK + package versions)
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v4 # v4
with:
persist-credentials: false
- name: Check SDK ↔ engine coupling
run: python3 scripts/check-sdk-versions.py
- name: Check npm package ↔ engine version coupling
run: python3 scripts/check-package-versions.py
build:
name: Build ${{ matrix.artifact || matrix.target }}
needs: sdk-gate
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- target: x86_64-unknown-linux-gnu
os: ubuntu-22.04
- target: x86_64-unknown-linux-gnu
artifact: x86_64-unknown-linux-gnu-cuda
os: ubuntu-22.04
cargo_features: ort-cuda
- target: aarch64-unknown-linux-gnu
os: ubuntu-22.04
cross: true
- target: x86_64-unknown-linux-musl
os: ubuntu-22.04
musl: true
- target: aarch64-unknown-linux-musl
os: ubuntu-22.04
musl: true
cross: true
zig: true
- target: x86_64-apple-darwin
os: macos-latest
- target: aarch64-apple-darwin
os: macos-latest
- target: x86_64-pc-windows-msvc
os: windows-latest
- target: x86_64-pc-windows-gnu
os: windows-latest
jemalloc: true
steps:
- uses: actions/checkout@v4 # v4
with:
persist-credentials: false
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
with:
targets: ${{ matrix.target }}
- name: Install musl tools
if: matrix.musl == true && matrix.zig != true
run: |
sudo apt-get update
sudo apt-get install -y musl-tools
if [[ "${{ matrix.target }}" == aarch64-* ]]; then
TOOLCHAIN_ROOT="${RUNNER_TEMP}/aarch64-linux-musl-cross"
curl -sL "https://musl.cc/aarch64-linux-musl-cross.tgz" | tar xz -C "${RUNNER_TEMP}"
echo "${TOOLCHAIN_ROOT}/bin" >> $GITHUB_PATH
echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER=aarch64-linux-musl-gcc" >> $GITHUB_ENV
echo "CC_aarch64_unknown_linux_musl=aarch64-linux-musl-gcc" >> $GITHUB_ENV
echo "AR_aarch64_unknown_linux_musl=aarch64-linux-musl-ar" >> $GITHUB_ENV
echo "RANLIB_aarch64_unknown_linux_musl=aarch64-linux-musl-ranlib" >> $GITHUB_ENV
fi
- name: Install cross-compilation tools
if: matrix.cross == true && matrix.musl != true
run: |
sudo apt-get update
sudo apt-get install -y gcc-aarch64-linux-gnu
echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc" >> $GITHUB_ENV
# ---- Windows jemalloc target: MSYS2 + pre-built jemalloc ------------
# See .github/actions/windows-jemalloc for the pitfall details.
- name: Setup MSYS2 and MinGW
if: matrix.jemalloc == true
uses: msys2/setup-msys2@e9898307ac31d1a803454791be09ab9973336e1c # v2
with:
msystem: MINGW64
update: true
install: base-devel mingw-w64-x86_64-toolchain
path-type: inherit
- name: Build jemalloc (Windows GNU target)
if: matrix.jemalloc == true
id: jemalloc
uses: ./.github/actions/windows-jemalloc
- name: Run tests
if: matrix.cross != true
working-directory: rust
shell: bash
run: |
if [[ "${{ matrix.jemalloc }}" == "true" ]]; then
JEMALLOC_OVERRIDE="${{ steps.jemalloc.outputs.jemalloc-override }}" \
CARGO_TARGET_X86_64_PC_WINDOWS_GNU_RUSTFLAGS="-L ${{ steps.jemalloc.outputs.dummy-dir }}" \
cargo test --lib --target x86_64-pc-windows-gnu --all-features --locked -- --test-threads=1
else
cargo test --lib --all-features --locked -- --test-threads=1
fi
- name: Setup Zig
if: matrix.zig == true
run: |
pip3 install -q ziglang==0.13.0
ZIG_BIN="$(python3 -c 'import ziglang, os; print(os.path.dirname(ziglang.__file__))')"
echo "${ZIG_BIN}" >> $GITHUB_PATH
"${ZIG_BIN}/zig" version
- name: Install cargo-zigbuild
if: matrix.zig == true
uses: taiki-e/install-action@74e87cbfa15a59692b158178d8905a61bf6fca95 # v2
with:
tool: cargo-zigbuild
# Ship the production feature set (Cargo default features incl. secure-update),
# NOT --all-features — the latter would pull dev-only tooling (dev-tools:
# gen_docs/gen_mcp_manifest) into the released binary. Default features are
# already exercised by the CI test job (which runs the --all-features superset).
# --locked pins Cargo.lock so releases are reproducible (no silent dep drift).
- name: Build
working-directory: rust
shell: bash
run: |
features="${{ matrix.cargo_features }}"
feature_args=()
if [[ -n "$features" ]]; then
feature_args+=(--features "$features")
fi
if [[ "${{ matrix.zig }}" == "true" ]]; then
cargo zigbuild --release --locked --target ${{ matrix.target }} "${feature_args[@]}"
elif [[ "${{ matrix.jemalloc }}" == "true" ]]; then
JEMALLOC_OVERRIDE="${{ steps.jemalloc.outputs.jemalloc-override }}" \
CARGO_TARGET_X86_64_PC_WINDOWS_GNU_RUSTFLAGS="-L ${{ steps.jemalloc.outputs.dummy-dir }}" \
cargo build --release --locked --target ${{ matrix.target }} "${feature_args[@]}"
else
cargo build --release --locked --target ${{ matrix.target }} "${feature_args[@]}"
fi
- name: Package (Unix)
if: runner.os != 'Windows'
shell: bash
run: |
artifact="${{ matrix.artifact || matrix.target }}"
cd rust/target/${{ matrix.target }}/release
tar czf ../../../../lean-ctx-${artifact}.tar.gz lean-ctx
cd ../../../..
- name: Package (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: |
Compress-Archive -Path "rust/target/${{ matrix.target }}/release/lean-ctx.exe" -DestinationPath "lean-ctx-${{ matrix.artifact || matrix.target }}.zip"
- name: Upload artifact
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: lean-ctx-${{ matrix.artifact || matrix.target }}
path: |
lean-ctx-${{ matrix.artifact || matrix.target }}.tar.gz
lean-ctx-${{ matrix.artifact || matrix.target }}.zip
release:
name: Create Release
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4 # v4
with:
persist-credentials: false
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
merge-multiple: true
- name: Create stable source tarball
run: |
VERSION="${GITHUB_REF_NAME#v}"
git archive --format=tar.gz --prefix="lean-ctx-${VERSION}/" "${GITHUB_REF_NAME}" -o "lean-ctx-${VERSION}-source.tar.gz"
- name: Generate checksums
run: sha256sum lean-ctx-*.tar.gz lean-ctx-*.zip > SHA256SUMS
- name: Extract release notes from CHANGELOG
id: notes
run: |
VERSION="${GITHUB_REF_NAME#v}"
PREV_TAG=$(git tag --sort=-v:refname | grep -A1 "^v${VERSION}$" | tail -1)
NOTES=$(awk "/^## \\[${VERSION}\\]/{found=1; next} /^## \\[/{if(found) exit} found{print}" CHANGELOG.md)
{
echo "body<<BODY_EOF"
echo "$NOTES"
echo ""
echo "#### Upgrade"
echo '```bash'
echo "lean-ctx update # recommended (auto-downloads + refreshes shell hooks)"
echo "cargo install lean-ctx # or"
echo "npm update -g lean-ctx-bin # or"
echo "brew upgrade lean-ctx"
echo '```'
echo ""
echo "> **Note:** After upgrading via cargo/npm/brew, run \`lean-ctx setup\` to refresh shell aliases. \`lean-ctx update\` does this automatically."
echo ""
echo "**Full Changelog**: https://github.com/yvgude/lean-ctx/compare/${PREV_TAG}...v${VERSION}"
echo "BODY_EOF"
} >> "$GITHUB_OUTPUT"
- name: Create GitHub Release
uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2
with:
body: ${{ steps.notes.outputs.body }}
files: |
lean-ctx-*.tar.gz
lean-ctx-*.zip
SHA256SUMS
jetbrains-plugin:
# Build the JetBrains/IntelliJ plugin and attach it to the GitHub Release as a
# downloadable .zip (#418). The plugin's own workflow only runs on plugin source
# changes; the GITHUB_TOKEN-created release here does not trigger other workflows,
# so the asset must be produced from inside this release pipeline. `-Pversion`
# mirrors the tag so the plugin version never drifts from the engine release.
name: Attach JetBrains Plugin
needs: release
runs-on: ubuntu-latest
if: ${{ !contains(github.ref_name, '-rc') }}
permissions:
contents: write
steps:
- uses: actions/checkout@v4 # v4
with:
persist-credentials: false
- name: Setup Java
uses: actions/setup-java@v5
with:
distribution: zulu
java-version: 21
- name: Setup Gradle
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6
- name: Build plugin distribution
working-directory: packages/jetbrains-lean-ctx
run: ./gradlew buildPlugin -Pversion="${GITHUB_REF_NAME#v}" --console=plain
- name: Attach plugin ZIP to release
env:
GH_TOKEN: ${{ github.token }}
# Gradle's rootProject.name is "lean-ctx", so buildPlugin emits
# build/distributions/lean-ctx-<version>.zip — indistinguishable from a
# source archive in the release asset list, which is why the plugin
# looked "missing" (#418). Re-name it to a discoverable, unambiguous
# asset before upload so users can actually find the JetBrains plugin.
run: |
VERSION="${GITHUB_REF_NAME#v}"
SRC="$(ls packages/jetbrains-lean-ctx/build/distributions/*.zip | head -1)"
if [ -z "$SRC" ]; then
echo "::error::No plugin distribution zip produced by buildPlugin" >&2
exit 1
fi
DEST="lean-ctx-jetbrains-plugin-${VERSION}.zip"
cp "$SRC" "$DEST"
gh release upload "${GITHUB_REF_NAME}" "$DEST" --clobber
publish-crates:
name: Publish to crates.io
needs: release
runs-on: ubuntu-latest
if: ${{ !contains(github.ref_name, '-rc') }}
steps:
- uses: actions/checkout@v4 # v4
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
# Fail the release if publishing fails — a silent skip used to ship a GitHub
# release whose crates.io artifact was missing. Re-runs are idempotent: an
# already-published version is treated as a no-op, not a failure.
- name: Publish
working-directory: rust
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
run: |
set -euo pipefail
CRATE_VERSION="$(grep -m1 '^version' Cargo.toml | sed -E 's/.*"([^"]+)".*/\1/')"
if curl -fsSL -H "User-Agent: lean-ctx-release" \
"https://crates.io/api/v1/crates/lean-ctx/${CRATE_VERSION}" >/dev/null 2>&1; then
echo "crates.io already has lean-ctx ${CRATE_VERSION} — skipping publish."
exit 0
fi
# --no-verify: rmcp 2.2.0 on crates.io has a compile bug (from_bytes_stream
# vs from_byte_stream); our [patch.crates-io] fixes it but is stripped by
# cargo package. Binary builds use --locked and are unaffected.
cargo publish --allow-dirty --no-verify --token "${CARGO_REGISTRY_TOKEN}"
publish-npm:
name: Publish to npm
needs: release
runs-on: ubuntu-latest
if: ${{ !contains(github.ref_name, '-rc') }}
steps:
- uses: actions/checkout@v4 # v4
with:
persist-credentials: false
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 22
registry-url: https://registry.npmjs.org
# Fail-hard on publish errors; idempotent on an already-published version
# (npm view exits non-zero when the exact version is absent). The version is
# read from package.json so the guard always matches what npm will publish.
- name: Publish lean-ctx-bin
working-directory: packages/lean-ctx-bin
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: |
set -euo pipefail
PKG_VERSION="$(node -p "require('./package.json').version")"
if npm view "lean-ctx-bin@${PKG_VERSION}" version >/dev/null 2>&1; then
echo "npm already has lean-ctx-bin@${PKG_VERSION} — skipping publish."
exit 0
fi
npm publish --access public
- name: Publish pi-lean-ctx
working-directory: packages/pi-lean-ctx
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: |
set -euo pipefail
PKG_VERSION="$(node -p "require('./package.json').version")"
if npm view "pi-lean-ctx@${PKG_VERSION}" version >/dev/null 2>&1; then
echo "npm already has pi-lean-ctx@${PKG_VERSION} — skipping publish."
exit 0
fi
# prepack builds the self-contained vendor bundle (esbuild devDep),
# so the published package ships with zero runtime dependencies (#670).
npm ci
npm publish --access public
update-homebrew:
name: Update Homebrew
needs: release
runs-on: ubuntu-latest
if: ${{ !contains(github.ref_name, '-rc') }}
steps:
- name: Download checksums and extract platform hashes
id: checksums
run: |
VERSION="${GITHUB_REF_NAME#v}"
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
gh release download "${GITHUB_REF_NAME}" \
--repo yvgude/lean-ctx --pattern "SHA256SUMS" --dir .
AARCH64_DARWIN=$(grep "aarch64-apple-darwin\.tar\.gz" SHA256SUMS | awk '{print $1}')
X86_64_DARWIN=$(grep "x86_64-apple-darwin\.tar\.gz" SHA256SUMS | awk '{print $1}')
AARCH64_LINUX_GNU=$(grep "aarch64-unknown-linux-gnu\.tar\.gz" SHA256SUMS | awk '{print $1}')
X86_64_LINUX_GNU=$(grep "x86_64-unknown-linux-gnu\.tar\.gz" SHA256SUMS | awk '{print $1}')
echo "aarch64_darwin_sha=${AARCH64_DARWIN}" >> "$GITHUB_OUTPUT"
echo "x86_64_darwin_sha=${X86_64_DARWIN}" >> "$GITHUB_OUTPUT"
echo "aarch64_linux_sha=${AARCH64_LINUX_GNU}" >> "$GITHUB_OUTPUT"
echo "x86_64_linux_sha=${X86_64_LINUX_GNU}" >> "$GITHUB_OUTPUT"
env:
GH_TOKEN: ${{ github.token }}
- name: Clone and generate binary formula
run: |
VERSION="${{ steps.checksums.outputs.version }}"
AARCH64_DARWIN_SHA="${{ steps.checksums.outputs.aarch64_darwin_sha }}"
X86_64_DARWIN_SHA="${{ steps.checksums.outputs.x86_64_darwin_sha }}"
AARCH64_LINUX_SHA="${{ steps.checksums.outputs.aarch64_linux_sha }}"
X86_64_LINUX_SHA="${{ steps.checksums.outputs.x86_64_linux_sha }}"
git clone "https://x-access-token:${HOMEBREW_TOKEN}@github.com/yvgude/homebrew-lean-ctx.git"
cd homebrew-lean-ctx
cat > Formula/lean-ctx.rb <<EOF
class LeanCtx < Formula
desc "The Context Engineering Layer for AI Coding — 71 MCP tools, 10 read modes, 95+ shell patterns"
homepage "https://leanctx.com"
version "${VERSION}"
license "Apache-2.0"
# Semantic search (ctx_semantic_search / embeddings) loads
# libonnxruntime at runtime; the engine resolves it from the
# Homebrew prefix lib dir. Without this dependency the dylib is
# absent and ORT init fails. See issue #544.
depends_on "onnxruntime"
on_macos do
if Hardware::CPU.arm?
url "https://github.com/yvgude/lean-ctx/releases/download/v${VERSION}/lean-ctx-aarch64-apple-darwin.tar.gz"
sha256 "${AARCH64_DARWIN_SHA}"
else
url "https://github.com/yvgude/lean-ctx/releases/download/v${VERSION}/lean-ctx-x86_64-apple-darwin.tar.gz"
sha256 "${X86_64_DARWIN_SHA}"
end
end
on_linux do
if Hardware::CPU.arm?
url "https://github.com/yvgude/lean-ctx/releases/download/v${VERSION}/lean-ctx-aarch64-unknown-linux-gnu.tar.gz"
sha256 "${AARCH64_LINUX_SHA}"
else
url "https://github.com/yvgude/lean-ctx/releases/download/v${VERSION}/lean-ctx-x86_64-unknown-linux-gnu.tar.gz"
sha256 "${X86_64_LINUX_SHA}"
end
end
def install
bin.install "lean-ctx"
end
test do
assert_match "lean-ctx ${VERSION}", shell_output("#{bin}/lean-ctx --version")
end
end
EOF
sed -i 's/^ //' Formula/lean-ctx.rb
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add Formula/lean-ctx.rb
git diff --cached --quiet || git commit -m "lean-ctx ${VERSION}"
git push
env:
HOMEBREW_TOKEN: ${{ secrets.HOMEBREW_GITHUB_TOKEN }}
announce-twitter:
name: Announce on Twitter
needs: release
runs-on: ubuntu-latest
if: ${{ !contains(github.ref_name, '-rc') }}
steps:
- uses: actions/checkout@v4 # v4
with:
persist-credentials: false
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 22
- name: Post release tweet
env:
TWITTER_CONSUMER_KEY: ${{ secrets.TWITTER_CONSUMER_KEY }}
TWITTER_CONSUMER_SECRET: ${{ secrets.TWITTER_CONSUMER_SECRET }}
TWITTER_ACCESS_TOKEN: ${{ secrets.TWITTER_ACCESS_TOKEN }}
TWITTER_ACCESS_SECRET: ${{ secrets.TWITTER_ACCESS_SECRET }}
RELEASE_TAG: ${{ github.ref_name }}
REPO: ${{ github.repository }}
run: node .github/scripts/post-release-tweet.mjs