rlm-cli 1.3.1

Recursive Language Model (RLM) REPL for Claude Code - handles long-context tasks via chunking and recursive sub-LLM calls
Documentation
---
name: Release

# Attested delivery: build platform binaries, attach SLSA build provenance
# to each, then fail-closed verify every attestation BEFORE the GitHub
# Release is published. A tag publishes nothing unattested.
#
# Dry-run: `workflow_dispatch` from any branch exercises the full
# build -> attest -> verify chain; the publish job is tag-gated and skipped.

"on":
  push:
    tags:
      - "v*.*.*"
  workflow_dispatch:

permissions:
  contents: read

env:
  CARGO_TERM_COLOR: always

jobs:
  version:
    name: Resolve Version
    runs-on: ubuntu-latest
    outputs:
      version: ${{ steps.get_version.outputs.version }}
    steps:
      - name: Checkout repository
        # yamllint disable-line rule:line-length
        uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10  # v6.0.3

      - name: Get version from tag or Cargo.toml
        id: get_version
        run: |
          if [[ "$GITHUB_REF" == refs/tags/v* ]]; then
            echo "version=${GITHUB_REF#refs/tags/v}" >> "$GITHUB_OUTPUT"
          else
            # Dry-run (workflow_dispatch): version from Cargo.toml, marked -dev
            v=$(grep -m1 '^version' Cargo.toml | cut -d'"' -f2)
            echo "version=${v}-dev" >> "$GITHUB_OUTPUT"
          fi

  build:
    name: Build (${{ matrix.platform }})
    needs: [version]
    runs-on: ${{ matrix.os }}
    permissions:
      contents: read
      id-token: write
      attestations: write
    strategy:
      fail-fast: false
      matrix:
        include:
          - os: ubuntu-latest
            platform: linux-amd64
            ext: ""
          - os: ubuntu-24.04-arm
            platform: linux-arm64
            ext: ""
          # macos-amd64 is intentionally absent: ort (onnxruntime, via the
          # default fastembed feature) ships no prebuilt binaries for
          # x86_64-apple-darwin, so Intel macOS cannot build this crate.
          - os: macos-latest
            platform: macos-arm64
            ext: ""
          - os: windows-latest
            platform: windows-amd64
            ext: ".exe"
    steps:
      - name: Checkout repository
        # yamllint disable-line rule:line-length
        uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10  # v6.0.3

      - name: Install Rust toolchain
        # yamllint disable-line rule:line-length
        uses: dtolnay/rust-toolchain@3c5f7ea28cd621ae0bf5283f0e981fb97b8a7af9  # master
        with:
          toolchain: stable

      - name: Build release binary
        run: cargo build --release --locked

      - name: Stage named artifact
        shell: bash
        env:
          VERSION: ${{ needs.version.outputs.version }}
          PLATFORM: ${{ matrix.platform }}
          EXT: ${{ matrix.ext }}
        run: |
          mkdir -p dist
          cp "target/release/rlm-cli${EXT}" \
            "dist/rlm-cli-${VERSION}-${PLATFORM}${EXT}"

      - name: Attest build provenance
        # yamllint disable-line rule:line-length
        uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32  # v4.1.0
        with:
          subject-path: dist/*

      - name: Upload artifact
        # yamllint disable-line rule:line-length
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a  # v7.0.1
        with:
          # yamllint disable-line rule:line-length
          name: rlm-cli-${{ needs.version.outputs.version }}-${{ matrix.platform }}
          path: dist/*
          if-no-files-found: error

  test:
    # Tags are not guaranteed to point at CI-green commits; the release
    # publishes nothing untested.
    name: Test
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        # yamllint disable-line rule:line-length
        uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10  # v6.0.3

      - name: Install Rust toolchain
        # yamllint disable-line rule:line-length
        uses: dtolnay/rust-toolchain@3c5f7ea28cd621ae0bf5283f0e981fb97b8a7af9  # master
        with:
          toolchain: stable

      - name: Run tests
        run: cargo test --all-features --locked

  audit:
    name: Cargo Audit
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        # yamllint disable-line rule:line-length
        uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10  # v6.0.3

      - name: Install cargo-audit
        # yamllint disable-line rule:line-length
        uses: taiki-e/install-action@0631aa6515c7d545823c67cfae7ef4fc7f490154  # v2.81.8
        with:
          tool: cargo-audit

      - name: Audit dependencies for known vulnerabilities
        run: cargo audit

  sbom:
    name: SBOM (generate + attest)
    needs: [version, build]
    runs-on: ubuntu-latest
    permissions:
      contents: read
      id-token: write
      attestations: write
    steps:
      - name: Checkout repository
        # yamllint disable-line rule:line-length
        uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10  # v6.0.3

      - name: Download all binaries
        # yamllint disable-line rule:line-length
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c  # v8.0.1
        with:
          path: dist
          merge-multiple: true

      - name: Generate CycloneDX SBOM
        # yamllint disable-line rule:line-length
        uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610  # v0.24.0
        with:
          path: .
          format: cyclonedx-json
          # yamllint disable-line rule:line-length
          output-file: rlm-cli-${{ needs.version.outputs.version }}-sbom.cdx.json
          upload-artifact: false
          upload-release-assets: false

      - name: Attest SBOM (binds every binary to the SBOM)
        # yamllint disable-line rule:line-length
        uses: actions/attest-sbom@c604332985a26aa8cf1bdc465b92731239ec6b9e  # v4.1.0
        with:
          subject-path: dist/*
          # yamllint disable-line rule:line-length
          sbom-path: rlm-cli-${{ needs.version.outputs.version }}-sbom.cdx.json

      - name: Upload SBOM artifact
        # yamllint disable-line rule:line-length
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a  # v7.0.1
        with:
          name: rlm-cli-${{ needs.version.outputs.version }}-sbom
          path: rlm-cli-${{ needs.version.outputs.version }}-sbom.cdx.json
          if-no-files-found: error

  verify:
    name: Verify Attestations
    needs: [version, build, sbom]
    runs-on: ubuntu-latest
    permissions:
      contents: read
      attestations: read
    steps:
      - name: Download all artifacts
        # yamllint disable-line rule:line-length
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c  # v8.0.1
        with:
          path: dist
          merge-multiple: true

      - name: Fail-closed attestation verification
        env:
          GH_TOKEN: ${{ github.token }}
        run: |
          set -euo pipefail
          shopt -s nullglob
          files=(dist/*)
          # Every platform binary plus the SBOM must be present — a
          # partial set must never reach the release.
          if [ "${#files[@]}" -ne 5 ]; then
            echo "::error::expected 5 artifacts, found ${#files[@]}"
            exit 1
          fi
          for f in "${files[@]}"; do
            case "$f" in
              *-sbom.cdx.json) continue ;;
            esac
            echo "Verifying provenance: ${f}"
            gh attestation verify "$f" --repo "$GITHUB_REPOSITORY"
            echo "Verifying SBOM attestation: ${f}"
            gh attestation verify "$f" --repo "$GITHUB_REPOSITORY" \
              --predicate-type https://cyclonedx.org/bom
          done

  release:
    name: Create Release
    if: startsWith(github.ref, 'refs/tags/')
    needs: [version, verify, audit, test]
    runs-on: ubuntu-latest
    environment: copilot
    permissions:
      contents: write
    steps:
      - name: Download all artifacts
        # yamllint disable-line rule:line-length
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c  # v8.0.1
        with:
          path: dist
          merge-multiple: true

      - name: Generate checksums
        env:
          VERSION: ${{ needs.version.outputs.version }}
        run: |
          cd dist
          sha256sum -- * > "rlm-cli-${VERSION}-checksums.txt"

      - name: Create GitHub Release
        # yamllint disable-line rule:line-length
        uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda  # v2.0.3
        with:
          tag_name: ${{ github.ref }}
          name: Release ${{ needs.version.outputs.version }}
          generate_release_notes: true
          draft: false
          prerelease: false
          files: dist/*
        env:
          # Use PAT so release event propagates to
          # downstream workflows (homebrew)
          GITHUB_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }}