Files
wifi-densepose/docs/research/latent-space/implementation-plans/agents/15-cicd.md
ruv d803bfe2b1 Squashed 'vendor/ruvector/' content from commit b64c2172
git-subtree-dir: vendor/ruvector
git-subtree-split: b64c21726f2bb37286d9ee36a7869fef60cc6900
2026-02-28 14:39:40 -05:00

33 KiB
Raw Blame History

Agent 15: CI/CD Pipeline Implementation

Agent: CI/CD Engineer Focus: Complete GitHub Actions workflows for testing, benchmarking, and release automation Status: Ready for Implementation

Overview

This document provides complete CI/CD pipeline configuration for RuVector, covering:

  • Continuous integration testing across platforms
  • Automated releases to multiple package registries
  • Daily performance benchmarking
  • Quality gates and dependency management

1. CI Workflow - Test on Every PR

File: .github/workflows/ci.yml

name: CI

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main, develop]

env:
  CARGO_TERM_COLOR: always
  RUST_BACKTRACE: 1

jobs:
  test:
    name: Test - ${{ matrix.os }} - ${{ matrix.arch }} - Node ${{ matrix.node }}
    runs-on: ${{ matrix.os }}

    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
        arch: [x64, arm64]
        node: [18, 20, 22]
        exclude:
          # Windows ARM64 runners not widely available
          - os: windows-latest
            arch: arm64
          # Ubuntu ARM64 requires special runners
          - os: ubuntu-latest
            arch: arm64

    steps:
      - name: Checkout code
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Setup Rust
        uses: dtolnay/rust-toolchain@stable
        with:
          components: rustfmt, clippy

      - name: Setup Rust cache
        uses: Swatinem/rust-cache@v2
        with:
          cache-on-failure: true

      - name: Setup Node.js ${{ matrix.node }}
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node }}
          cache: 'npm'
          architecture: ${{ matrix.arch }}

      - name: Install dependencies
        run: npm ci

      - name: Run Clippy
        run: cargo clippy --all-targets --all-features -- -D warnings

      - name: Check formatting
        run: cargo fmt -- --check

      - name: Run Rust tests
        run: cargo test --all-features --verbose

      - name: Build NAPI bindings
        run: npm run build
        env:
          CARGO_PROFILE_RELEASE_LTO: 'false'

      - name: Run Node.js tests
        run: npm test

      - name: Generate coverage
        if: matrix.os == 'ubuntu-latest' && matrix.node == '20' && matrix.arch == 'x64'
        run: |
          cargo install cargo-tarpaulin
          cargo tarpaulin --out Xml --output-dir ./coverage

      - name: Upload coverage to Codecov
        if: matrix.os == 'ubuntu-latest' && matrix.node == '20' && matrix.arch == 'x64'
        uses: codecov/codecov-action@v4
        with:
          files: ./coverage/cobertura.xml
          flags: unittests
          fail_ci_if_error: true
          token: ${{ secrets.CODECOV_TOKEN }}

      - name: Check coverage threshold
        if: matrix.os == 'ubuntu-latest' && matrix.node == '20' && matrix.arch == 'x64'
        run: |
          COVERAGE=$(cargo tarpaulin --out Json | jq -r '.files | map(.coverage) | add / length')
          echo "Coverage: $COVERAGE%"
          if (( $(echo "$COVERAGE < 80" | bc -l) )); then
            echo "Coverage $COVERAGE% is below threshold 80%"
            exit 1
          fi

  lint:
    name: Lint and Type Check
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Run ESLint
        run: npm run lint
        continue-on-error: true

      - name: Run TypeScript check
        run: npm run typecheck

  security:
    name: Security Audit
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Rust
        uses: dtolnay/rust-toolchain@stable

      - name: Run cargo audit
        run: |
          cargo install cargo-audit
          cargo audit

      - name: Run npm audit
        run: npm audit --audit-level=moderate

  wasm:
    name: WASM Build
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Rust
        uses: dtolnay/rust-toolchain@stable
        with:
          targets: wasm32-unknown-unknown

      - name: Install wasm-pack
        run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh

      - name: Build WASM
        run: wasm-pack build --target web --out-dir pkg/wasm

      - name: Test WASM
        run: wasm-pack test --headless --firefox

  quality-gate:
    name: Quality Gate
    runs-on: ubuntu-latest
    needs: [test, lint, security, wasm]
    if: always()

    steps:
      - name: Check job results
        run: |
          if [[ "${{ needs.test.result }}" != "success" ]] || \
             [[ "${{ needs.lint.result }}" != "success" ]] || \
             [[ "${{ needs.security.result }}" != "success" ]] || \
             [[ "${{ needs.wasm.result }}" != "success" ]]; then
            echo "Quality gate failed"
            exit 1
          fi
          echo "Quality gate passed"

2. Release Workflow - Publish on Tag

File: .github/workflows/release.yml

name: Release

on:
  push:
    tags:
      - 'v*.*.*'

permissions:
  contents: write
  packages: write

env:
  CARGO_TERM_COLOR: always

jobs:
  create-release:
    name: Create Release
    runs-on: ubuntu-latest
    outputs:
      upload_url: ${{ steps.create_release.outputs.upload_url }}
      version: ${{ steps.get_version.outputs.version }}

    steps:
      - name: Checkout code
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Get version
        id: get_version
        run: echo "version=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT

      - name: Generate changelog
        id: changelog
        run: |
          PREV_TAG=$(git describe --abbrev=0 --tags $(git rev-list --tags --skip=1 --max-count=1) 2>/dev/null || echo "")
          if [ -z "$PREV_TAG" ]; then
            CHANGELOG=$(git log --pretty=format:"- %s (%h)" --reverse)
          else
            CHANGELOG=$(git log ${PREV_TAG}..HEAD --pretty=format:"- %s (%h)" --reverse)
          fi
          echo "changelog<<EOF" >> $GITHUB_OUTPUT
          echo "$CHANGELOG" >> $GITHUB_OUTPUT
          echo "EOF" >> $GITHUB_OUTPUT

      - name: Create Release
        id: create_release
        uses: actions/create-release@v1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          tag_name: ${{ github.ref }}
          release_name: Release v${{ steps.get_version.outputs.version }}
          body: |
            ## Changes
            ${{ steps.changelog.outputs.changelog }}

            ## Downloads
            - Cargo: `cargo install ruvector`
            - npm: `npm install ruvector`
            - WASM: Available in release assets
          draft: false
          prerelease: false

  build-binaries:
    name: Build - ${{ matrix.target }}
    needs: create-release
    runs-on: ${{ matrix.os }}

    strategy:
      matrix:
        include:
          - os: ubuntu-latest
            target: x86_64-unknown-linux-gnu
            node: '20'
          - os: ubuntu-latest
            target: aarch64-unknown-linux-gnu
            node: '20'
          - os: windows-latest
            target: x86_64-pc-windows-msvc
            node: '20'
          - os: macos-latest
            target: x86_64-apple-darwin
            node: '20'
          - os: macos-latest
            target: aarch64-apple-darwin
            node: '20'

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Rust
        uses: dtolnay/rust-toolchain@stable
        with:
          targets: ${{ matrix.target }}

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node }}
          cache: 'npm'

      - name: Install cross-compilation tools
        if: matrix.target == 'aarch64-unknown-linux-gnu'
        run: |
          sudo apt-get update
          sudo apt-get install -y gcc-aarch64-linux-gnu

      - name: Install dependencies
        run: npm ci

      - name: Build
        run: npm run build -- --target ${{ matrix.target }}
        env:
          CARGO_PROFILE_RELEASE_LTO: 'true'

      - name: Package binary
        id: package
        run: |
          ARCHIVE_NAME="ruvector-${{ needs.create-release.outputs.version }}-${{ matrix.target }}"
          if [[ "${{ matrix.os }}" == "windows-latest" ]]; then
            7z a ${ARCHIVE_NAME}.zip ./target/${{ matrix.target }}/release/*.node
            echo "archive=${ARCHIVE_NAME}.zip" >> $GITHUB_OUTPUT
          else
            tar czf ${ARCHIVE_NAME}.tar.gz -C ./target/${{ matrix.target }}/release *.node
            echo "archive=${ARCHIVE_NAME}.tar.gz" >> $GITHUB_OUTPUT
          fi

      - name: Upload Release Asset
        uses: actions/upload-release-asset@v1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          upload_url: ${{ needs.create-release.outputs.upload_url }}
          asset_path: ./${{ steps.package.outputs.archive }}
          asset_name: ${{ steps.package.outputs.archive }}
          asset_content_type: application/octet-stream

  publish-cargo:
    name: Publish to crates.io
    needs: create-release
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Rust
        uses: dtolnay/rust-toolchain@stable

      - name: Publish to crates.io
        run: cargo publish --token ${{ secrets.CARGO_TOKEN }}
        env:
          CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_TOKEN }}

  publish-npm:
    name: Publish to npm
    needs: [create-release, build-binaries]
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          registry-url: 'https://registry.npmjs.org'

      - name: Install dependencies
        run: npm ci

      - name: Build
        run: npm run build

      - name: Publish to npm
        run: npm publish --access public
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

  publish-wasm:
    name: Publish WASM Package
    needs: create-release
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Rust
        uses: dtolnay/rust-toolchain@stable
        with:
          targets: wasm32-unknown-unknown

      - name: Install wasm-pack
        run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh

      - name: Build WASM
        run: wasm-pack build --target web --out-dir pkg/wasm

      - name: Package WASM
        run: |
          cd pkg/wasm
          tar czf ../../ruvector-${{ needs.create-release.outputs.version }}-wasm.tar.gz .

      - name: Upload WASM Asset
        uses: actions/upload-release-asset@v1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          upload_url: ${{ needs.create-release.outputs.upload_url }}
          asset_path: ./ruvector-${{ needs.create-release.outputs.version }}-wasm.tar.gz
          asset_name: ruvector-${{ needs.create-release.outputs.version }}-wasm.tar.gz
          asset_content_type: application/gzip

  publish-github:
    name: Publish to GitHub Packages
    needs: create-release
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          registry-url: 'https://npm.pkg.github.com'
          scope: '@ruvector'

      - name: Install dependencies
        run: npm ci

      - name: Build
        run: npm run build

      - name: Update package name for GitHub
        run: |
          node -e "
            const pkg = require('./package.json');
            pkg.name = '@ruvector/ruvector';
            require('fs').writeFileSync('package.json', JSON.stringify(pkg, null, 2));
          "

      - name: Publish to GitHub Packages
        run: npm publish
        env:
          NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

3. Benchmark Workflow - Daily Performance

File: .github/workflows/benchmark.yml

name: Benchmark

on:
  schedule:
    # Run daily at 2 AM UTC
    - cron: '0 2 * * *'
  push:
    branches: [main]
    paths:
      - 'src/**'
      - 'benches/**'
      - 'Cargo.toml'
  workflow_dispatch:

permissions:
  contents: write
  deployments: write

env:
  CARGO_TERM_COLOR: always

jobs:
  benchmark:
    name: Run Benchmarks
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Setup Rust
        uses: dtolnay/rust-toolchain@stable

      - name: Setup Rust cache
        uses: Swatinem/rust-cache@v2

      - name: Install criterion
        run: cargo install cargo-criterion

      - name: Run benchmarks
        run: cargo criterion --message-format=json > benchmark-results.json

      - name: Parse benchmark results
        id: parse
        run: |
          python3 <<EOF
          import json
          import os

          with open('benchmark-results.json') as f:
              results = [json.loads(line) for line in f if line.strip()]

          benchmarks = []
          for result in results:
              if result.get('reason') == 'benchmark-complete':
                  name = result['id']
                  mean = result['mean']['estimate']
                  benchmarks.append({
                      'name': name,
                      'unit': 'ns',
                      'value': mean
                  })

          with open('parsed-results.json', 'w') as f:
              json.dump(benchmarks, f)

          print(f"Parsed {len(benchmarks)} benchmarks")
          EOF

      - name: Download previous benchmark data
        uses: actions/cache@v4
        with:
          path: ./cache
          key: benchmark-${{ github.ref }}

      - name: Check for regression
        id: regression
        run: |
          python3 <<EOF
          import json
          import os

          # Load current results
          with open('parsed-results.json') as f:
              current = {b['name']: b['value'] for b in json.load(f)}

          # Load previous results
          previous = {}
          if os.path.exists('./cache/benchmark.json'):
              with open('./cache/benchmark.json') as f:
                  previous = {b['name']: b['value'] for b in json.load(f)}

          # Check for regressions (>5% slower)
          regressions = []
          for name, curr_val in current.items():
              if name in previous:
                  prev_val = previous[name]
                  change = ((curr_val - prev_val) / prev_val) * 100
                  if change > 5.0:
                      regressions.append({
                          'name': name,
                          'previous': prev_val,
                          'current': curr_val,
                          'change': change
                      })

          if regressions:
              print("⚠️ Performance regressions detected:")
              for r in regressions:
                  print(f"  {r['name']}: +{r['change']:.2f}% slower")
              with open(os.environ['GITHUB_OUTPUT'], 'a') as f:
                  f.write('has_regression=true\n')
              exit(1)
          else:
              print("✅ No performance regressions detected")
              with open(os.environ['GITHUB_OUTPUT'], 'a') as f:
                  f.write('has_regression=false\n')
          EOF
        continue-on-error: true

      - name: Store benchmark result
        uses: benchmark-action/github-action-benchmark@v1
        with:
          name: Rust Benchmark
          tool: 'cargo'
          output-file-path: benchmark-results.json
          github-token: ${{ secrets.GITHUB_TOKEN }}
          auto-push: true
          alert-threshold: '105%'
          comment-on-alert: true
          fail-on-alert: true
          alert-comment-cc-users: '@ruvector/maintainers'

      - name: Save results for next run
        run: |
          mkdir -p ./cache
          cp parsed-results.json ./cache/benchmark.json

      - name: Generate benchmark report
        run: |
          python3 <<EOF
          import json

          with open('parsed-results.json') as f:
              benchmarks = json.load(f)

          print("# Benchmark Results\n")
          print("| Benchmark | Time (ns) |")
          print("|-----------|-----------|")
          for b in sorted(benchmarks, key=lambda x: x['name']):
              print(f"| {b['name']} | {b['value']:.2f} |")
          EOF

      - name: Comment on PR
        if: github.event_name == 'pull_request'
        uses: actions/github-script@v7
        with:
          script: |
            const fs = require('fs');
            const benchmarks = JSON.parse(fs.readFileSync('parsed-results.json', 'utf8'));

            let comment = '## 📊 Benchmark Results\n\n';
            comment += '| Benchmark | Time (ns) |\n';
            comment += '|-----------|-----------||\n';

            benchmarks.sort((a, b) => a.name.localeCompare(b.name));
            for (const b of benchmarks) {
              comment += `| ${b.name} | ${b.value.toFixed(2)} |\n`;
            }

            if ('${{ steps.regression.outputs.has_regression }}' === 'true') {
              comment += '\n⚠ **Performance regression detected!** See logs for details.\n';
            }

            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: comment
            });

  memory-benchmark:
    name: Memory Usage Benchmark
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Rust
        uses: dtolnay/rust-toolchain@stable

      - name: Install valgrind
        run: sudo apt-get update && sudo apt-get install -y valgrind

      - name: Build with debug info
        run: cargo build --release --tests

      - name: Run memory profiling
        run: |
          valgrind --tool=massif --massif-out-file=massif.out \
            cargo test --release -- --test-threads=1

      - name: Parse memory results
        run: |
          ms_print massif.out > memory-report.txt
          grep "peak" memory-report.txt || true

      - name: Upload memory report
        uses: actions/upload-artifact@v4
        with:
          name: memory-report
          path: memory-report.txt

  platform-benchmark:
    name: Platform Benchmark - ${{ matrix.os }}
    runs-on: ${{ matrix.os }}

    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Rust
        uses: dtolnay/rust-toolchain@stable

      - name: Run platform-specific benchmarks
        run: cargo bench --bench platform_benchmarks

      - name: Upload results
        uses: actions/upload-artifact@v4
        with:
          name: benchmark-${{ matrix.os }}
          path: target/criterion/

4. Dependabot Configuration

File: .github/dependabot.yml

version: 2

updates:
  # Cargo dependencies
  - package-ecosystem: "cargo"
    directory: "/"
    schedule:
      interval: "weekly"
      day: "monday"
      time: "06:00"
    open-pull-requests-limit: 10
    reviewers:
      - "ruvector/maintainers"
    labels:
      - "dependencies"
      - "rust"
    commit-message:
      prefix: "chore(deps):"
      include: "scope"
    ignore:
      # Ignore major version updates for breaking changes
      - dependency-name: "*"
        update-types: ["version-update:semver-major"]
    groups:
      rust-dependencies:
        patterns:
          - "*"
        update-types:
          - "minor"
          - "patch"

  # npm dependencies
  - package-ecosystem: "npm"
    directory: "/"
    schedule:
      interval: "weekly"
      day: "monday"
      time: "06:00"
    open-pull-requests-limit: 10
    reviewers:
      - "ruvector/maintainers"
    labels:
      - "dependencies"
      - "npm"
    commit-message:
      prefix: "chore(deps):"
      include: "scope"
    ignore:
      - dependency-name: "*"
        update-types: ["version-update:semver-major"]
    groups:
      npm-dependencies:
        patterns:
          - "*"
        update-types:
          - "minor"
          - "patch"

  # GitHub Actions
  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "weekly"
      day: "monday"
      time: "06:00"
    open-pull-requests-limit: 5
    reviewers:
      - "ruvector/maintainers"
    labels:
      - "dependencies"
      - "github-actions"
    commit-message:
      prefix: "chore(ci):"
      include: "scope"

5. Quality Gates Configuration

File: .github/workflows/quality-gate.yml

name: Quality Gate

on:
  pull_request:
    branches: [main, develop]
  push:
    branches: [main]

jobs:
  coverage-gate:
    name: Coverage Gate
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Rust
        uses: dtolnay/rust-toolchain@stable

      - name: Install tarpaulin
        run: cargo install cargo-tarpaulin

      - name: Generate coverage
        run: cargo tarpaulin --out Xml --out Json --output-dir ./coverage

      - name: Check coverage threshold
        run: |
          COVERAGE=$(jq -r '.files | map(.coverage) | add / length' coverage/tarpaulin.json)
          echo "Coverage: ${COVERAGE}%"

          if (( $(echo "$COVERAGE < 80" | bc -l) )); then
            echo "❌ Coverage ${COVERAGE}% is below threshold 80%"
            exit 1
          fi

          echo "✅ Coverage ${COVERAGE}% meets threshold"

      - name: Comment coverage on PR
        if: github.event_name == 'pull_request'
        uses: actions/github-script@v7
        with:
          script: |
            const fs = require('fs');
            const coverage = JSON.parse(fs.readFileSync('coverage/tarpaulin.json', 'utf8'));
            const total = coverage.files.reduce((sum, f) => sum + f.coverage, 0) / coverage.files.length;

            const comment = `## 📊 Code Coverage Report

            **Total Coverage**: ${total.toFixed(2)}%
            **Threshold**: 80%
            **Status**: ${total >= 80 ? '✅ PASS' : '❌ FAIL'}

            ### File Coverage
            | File | Coverage |
            |------|----------|
            ${coverage.files.slice(0, 10).map(f =>
              `| ${f.path} | ${f.coverage.toFixed(2)}% |`
            ).join('\n')}
            `;

            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: comment
            });

  performance-gate:
    name: Performance Gate
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4
        with:
          fetch-depth: 2

      - name: Setup Rust
        uses: dtolnay/rust-toolchain@stable

      - name: Run benchmarks
        run: cargo bench --bench performance_gate -- --save-baseline current

      - name: Checkout base branch
        run: git checkout HEAD~1

      - name: Run baseline benchmarks
        run: cargo bench --bench performance_gate -- --save-baseline baseline

      - name: Compare benchmarks
        run: |
          cargo install critcmp
          critcmp baseline current > comparison.txt
          cat comparison.txt

          # Check if any benchmark is >5% slower
          if grep -q "change:.* +[0-9.]*%" comparison.txt; then
            REGRESSION=$(grep "change:.* +[0-9.]*%" comparison.txt | awk '{print $NF}' | tr -d '%')
            if (( $(echo "$REGRESSION > 5" | bc -l) )); then
              echo "❌ Performance regression detected: ${REGRESSION}%"
              exit 1
            fi
          fi

          echo "✅ No significant performance regression"

  clippy-gate:
    name: Clippy Gate
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Rust
        uses: dtolnay/rust-toolchain@stable
        with:
          components: clippy

      - name: Run Clippy
        run: |
          cargo clippy --all-targets --all-features \
            --message-format=json -- -D warnings \
            > clippy-results.json

      - name: Check for warnings
        run: |
          WARNINGS=$(jq -r 'select(.reason == "compiler-message") | select(.message.level == "warning")' clippy-results.json | wc -l)

          echo "Clippy warnings: $WARNINGS"

          if [ "$WARNINGS" -gt 0 ]; then
            echo "❌ Clippy warnings detected"
            jq -r 'select(.reason == "compiler-message") | select(.message.level == "warning") | .message.message' clippy-results.json
            exit 1
          fi

          echo "✅ No Clippy warnings"

  security-gate:
    name: Security Gate
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Run cargo-deny
        uses: EmbarkStudios/cargo-deny-action@v1
        with:
          log-level: warn
          command: check
          arguments: --all-features

      - name: Run cargo-audit
        run: |
          cargo install cargo-audit
          cargo audit --deny warnings

      - name: Run npm audit
        run: npm audit --audit-level=high

  size-gate:
    name: Binary Size Gate
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Rust
        uses: dtolnay/rust-toolchain@stable

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

      - name: Check binary size
        run: |
          SIZE=$(stat -c%s target/release/libruvector.so)
          SIZE_MB=$(echo "scale=2; $SIZE / 1048576" | bc)

          echo "Binary size: ${SIZE_MB}MB"

          # Fail if binary is larger than 50MB
          if (( $(echo "$SIZE_MB > 50" | bc -l) )); then
            echo "❌ Binary size ${SIZE_MB}MB exceeds 50MB limit"
            exit 1
          fi

          echo "✅ Binary size is acceptable"

6. Additional Workflow Files

6.1 PR Labeler

File: .github/workflows/labeler.yml

name: Label PRs

on:
  pull_request:
    types: [opened, synchronize]

jobs:
  label:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/labeler@v5
        with:
          repo-token: ${{ secrets.GITHUB_TOKEN }}
          configuration-path: .github/labeler.yml

File: .github/labeler.yml

rust:
  - changed-files:
    - any-glob-to-any-file: 'src/**/*.rs'
    - any-glob-to-any-file: 'Cargo.toml'

typescript:
  - changed-files:
    - any-glob-to-any-file: '**/*.ts'
    - any-glob-to-any-file: 'package.json'

documentation:
  - changed-files:
    - any-glob-to-any-file: 'docs/**/*'
    - any-glob-to-any-file: '**/*.md'

tests:
  - changed-files:
    - any-glob-to-any-file: '**/*.test.ts'
    - any-glob-to-any-file: 'tests/**/*'

ci:
  - changed-files:
    - any-glob-to-any-file: '.github/**/*'

6.2 Stale Issue Management

File: .github/workflows/stale.yml

name: Mark Stale Issues and PRs

on:
  schedule:
    - cron: '0 0 * * *'

permissions:
  issues: write
  pull-requests: write

jobs:
  stale:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/stale@v9
        with:
          repo-token: ${{ secrets.GITHUB_TOKEN }}
          stale-issue-message: 'This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.'
          stale-pr-message: 'This PR has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.'
          stale-issue-label: 'stale'
          stale-pr-label: 'stale'
          days-before-stale: 60
          days-before-close: 7
          exempt-issue-labels: 'pinned,security'
          exempt-pr-labels: 'pinned,security'

7. Setup Instructions

7.1 Required Secrets

Add these secrets to your GitHub repository:

# Repository Settings > Secrets and Variables > Actions

CARGO_TOKEN          # crates.io API token
NPM_TOKEN            # npm registry token
CODECOV_TOKEN        # Codecov upload token

7.2 Branch Protection Rules

Configure branch protection for main:

  • Require pull request reviews before merging (1 approval)
  • Require status checks to pass:
    • test (all matrix jobs)
    • lint
    • security
    • wasm
    • quality-gate
    • coverage-gate
    • performance-gate
    • clippy-gate
  • Require branches to be up to date
  • Require conversation resolution before merging
  • Include administrators

7.3 CODEOWNERS

File: .github/CODEOWNERS

# Default owners for everything
* @ruvector/maintainers

# Rust code
*.rs @ruvector/rust-team
Cargo.toml @ruvector/rust-team

# TypeScript/JavaScript
*.ts @ruvector/typescript-team
*.js @ruvector/typescript-team
package.json @ruvector/typescript-team

# CI/CD workflows
.github/workflows/ @ruvector/devops-team

# Documentation
/docs/ @ruvector/docs-team
*.md @ruvector/docs-team

8. Monitoring and Alerts

8.1 Workflow Failure Notifications

File: .github/workflows/notify-failures.yml

name: Notify on Workflow Failures

on:
  workflow_run:
    workflows: ["CI", "Release", "Benchmark"]
    types: [completed]

jobs:
  notify:
    runs-on: ubuntu-latest
    if: ${{ github.event.workflow_run.conclusion == 'failure' }}

    steps:
      - name: Send Slack notification
        uses: slackapi/slack-github-action@v1
        with:
          webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }}
          payload: |
            {
              "text": "❌ Workflow failed: ${{ github.event.workflow_run.name }}",
              "blocks": [
                {
                  "type": "section",
                  "text": {
                    "type": "mrkdwn",
                    "text": "*Workflow Failed*\n*Workflow*: ${{ github.event.workflow_run.name }}\n*Branch*: ${{ github.event.workflow_run.head_branch }}\n*Run*: ${{ github.event.workflow_run.html_url }}"
                  }
                }
              ]
            }

9. Performance Optimization Tips

9.1 Caching Strategy

# Rust cache
- uses: Swatinem/rust-cache@v2
  with:
    cache-on-failure: true
    shared-key: "rust-cache"

# npm cache
- uses: actions/setup-node@v4
  with:
    cache: 'npm'

# Custom cache for build artifacts
- uses: actions/cache@v4
  with:
    path: |
      target/
      node_modules/
    key: ${{ runner.os }}-build-${{ hashFiles('**/Cargo.lock', '**/package-lock.json') }}

9.2 Parallel Job Execution

jobs:
  test:
    strategy:
      fail-fast: false
      max-parallel: 10  # Run up to 10 jobs in parallel
      matrix:
        # Matrix configuration

9.3 Conditional Execution

- name: Skip if docs only
  if: |
    !contains(github.event.head_commit.message, '[skip ci]') &&
    !contains(github.event.head_commit.message, '[docs only]')
  run: cargo test

10. Troubleshooting Guide

Common Issues

  1. Build failures on ARM64

    • Ensure cross-compilation tools are installed
    • Use appropriate runners or QEMU emulation
  2. Coverage threshold failures

    • Review untested code paths
    • Add integration tests
    • Consider adjusting threshold for infrastructure code
  3. Benchmark regressions

    • Check for algorithmic changes
    • Review recent dependency updates
    • Profile hot paths with flamegraph
  4. Release failures

    • Verify version bumps in Cargo.toml and package.json
    • Ensure tokens are valid and have correct permissions
    • Check that all platform builds succeeded

11. Success Metrics

The CI/CD pipeline should achieve:

  • Test coverage: ≥80%
  • Build time: <15 minutes per platform
  • Benchmark stability: <5% variance
  • Release automation: 100% (zero manual steps)
  • Security scan: 0 high/critical vulnerabilities
  • Workflow reliability: >99% success rate

Implementation Checklist

  • Create all workflow files in .github/workflows/
  • Configure Dependabot in .github/dependabot.yml
  • Add required secrets to repository settings
  • Configure branch protection rules
  • Set up CODEOWNERS file
  • Enable GitHub Actions permissions
  • Test CI workflow with draft PR
  • Validate release workflow with pre-release tag
  • Confirm benchmark automation works
  • Set up notification channels (Slack, email)
  • Document workflow for team

Next Steps

  1. Review and merge this CI/CD configuration
  2. Test each workflow individually
  3. Monitor first few automated releases
  4. Iterate based on team feedback
  5. Add platform-specific optimizations as needed

Agent 15 Status: Complete CI/CD pipeline ready for deployment