rlm-cli 1.4.0

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

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

permissions:
  contents: read

env:
  CARGO_TERM_COLOR: always

jobs:
  publish:
    name: Publish to crates.io
    runs-on: ubuntu-latest
    environment: copilot
    permissions:
      contents: read
      # OIDC for crates.io Trusted Publishing and .crate attestation signing
      id-token: write
      # Provenance attestation for the published .crate
      attestations: write
    steps:
      - name: Checkout repository
        # yamllint disable-line rule:line-length
        uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0  # v7.0.0

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

      - name: Install cargo-deny
        # yamllint disable-line rule:line-length
        uses: taiki-e/install-action@7a79fe8c3a13344501c80d99cae481c1c9085912  # v2.67.18
        with:
          tool: cargo-deny

      - name: Verify package
        run: |
          cargo package --list
          cargo package --allow-dirty

      # cargo publish ships the Cargo.toml version regardless of the tag;
      # publishing is irreversible, so a mismatch must fail before publish.
      - name: Assert tag version matches Cargo.toml
        if: startsWith(github.ref, 'refs/tags/v')
        run: |
          TAG_VERSION="${GITHUB_REF#refs/tags/v}"
          CARGO_VERSION=$(cargo metadata --no-deps --locked \
            --format-version 1 | jq -r '.packages[0].version')
          if [ "$TAG_VERSION" != "$CARGO_VERSION" ]; then
            echo "::error::tag v${TAG_VERSION} does not match" \
              "Cargo.toml version ${CARGO_VERSION}"
            exit 1
          fi

      - name: Run pre-publish checks
        run: |
          cargo fmt -- --check
          cargo clippy --all-targets --all-features --locked -- -D warnings
          cargo test --all-features --locked
          cargo doc --no-deps --all-features --locked
          cargo deny check

      - name: Dry run publish
        run: cargo publish --dry-run --allow-dirty

      # Trusted Publishing (OIDC): no long-lived registry token. Requires
      # the one-time crates.io setup — crate Settings -> Trusted Publishing
      # -> add GitHub repo zircote/rlm-rs, workflow publish.yml,
      # environment copilot.
      - name: Authenticate with crates.io
        id: auth
        if: startsWith(github.ref, 'refs/tags/v')
        # yamllint disable-line rule:line-length
        uses: rust-lang/crates-io-auth-action@c6f97d42243bad5fab37ca0427f495c86d5b1a18  # v1.0.5

      - name: Publish to crates.io
        if: startsWith(github.ref, 'refs/tags/v')
        run: cargo publish --token "$CARGO_REGISTRY_TOKEN"
        env:
          CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }}

      # Attest the bytes the registry actually serves, not a local rebuild:
      # download the published .crate, assert it matches what cargo packaged
      # locally (cargo packaging is deterministic for a given commit — a
      # mismatch means something is wrong and must fail loudly), then attach
      # SLSA provenance to it.
      - name: Download published crate from registry
        id: crate
        if: startsWith(github.ref, 'refs/tags/v')
        run: |
          VERSION="${GITHUB_REF#refs/tags/v}"
          CRATE="rlm-cli-${VERSION}.crate"
          URL="https://static.crates.io/crates/rlm-cli/${CRATE}"
          downloaded=0
          # static.crates.io can reject requests without a User-Agent
          for attempt in 1 2 3 4 5 6; do
            if curl -fsSL -A 'rlm-cli-release-check' \
              -o "${CRATE}" "$URL"; then
              downloaded=1
              break
            fi
            echo "crate not yet on CDN (attempt ${attempt}); retrying"
            sleep 10
          done
          if [ "$downloaded" -ne 1 ] || [ ! -s "${CRATE}" ]; then
            echo "::error::failed to download ${URL} after 6 attempts"
            exit 1
          fi
          LOCAL="target/package/${CRATE}"
          if [ ! -f "$LOCAL" ]; then
            echo "::error::locally packaged crate missing: ${LOCAL}"
            exit 1
          fi
          sha_remote=$(sha256sum "${CRATE}" | cut -d' ' -f1)
          sha_local=$(sha256sum "$LOCAL" | cut -d' ' -f1)
          if [ "$sha_remote" != "$sha_local" ]; then
            echo "::error::registry crate bytes differ from local package"
            exit 1
          fi
          echo "crate-path=${CRATE}" >> "$GITHUB_OUTPUT"

      - name: Attest crate provenance
        if: startsWith(github.ref, 'refs/tags/v')
        # yamllint disable-line rule:line-length
        uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373  # v4.1.1
        with:
          subject-path: ${{ steps.crate.outputs.crate-path }}