name: CI on: push: branches: [main] paths-ignore: - "**/*.md" - ".github/assets/**" - "LICENSE" - ".github/scripts/readme-demo.py" pull_request: types: [opened, synchronize, reopened, edited] paths-ignore: - "**/*.md" - ".github/assets/**" - "LICENSE" - ".github/scripts/readme-demo.py" workflow_dispatch: permissions: {} concurrency: group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || github.sha }} cancel-in-progress: true jobs: quality: name: quality runs-on: ubuntu-latest timeout-minutes: 15 permissions: contents: read steps: - uses: actions/checkout@v6 with: persist-credentials: false - uses: actions/setup-node@v6 with: node-version: 24 cache: npm - run: npm ci - run: npm run lint - run: npm run format:check - run: npm run typecheck - run: npm run build - name: Validate pull request title if: github.event_name == 'pull_request' env: PR_TITLE: ${{ github.event.pull_request.title }} run: >- node --input-type=module --eval "const title = process.env.PR_TITLE ?? ''; const valid = /^(feat|fix|perf|refactor|docs|test|build|ci|chore|revert)(\([^)]+\))?!?: .+/.test(title); if (!valid) { console.error('PR title must use Conventional Commits syntax'); process.exit(1); }" coverage: name: coverage runs-on: ubuntu-latest timeout-minutes: 25 permissions: contents: read steps: - uses: actions/checkout@v6 with: persist-credentials: false - uses: actions/setup-node@v6 with: node-version: 24 cache: npm - run: npm ci - run: npm run test:coverage package-candidate: name: package candidate runs-on: ubuntu-latest timeout-minutes: 20 permissions: contents: read steps: - uses: actions/checkout@v6 with: persist-credentials: false - uses: actions/setup-node@v6 with: node-version: 24 cache: npm - run: npm ci - name: Build the candidate tarball run: | mkdir -p "$RUNNER_TEMP/package" npm pack --json --pack-destination "$RUNNER_TEMP/package" - uses: actions/upload-artifact@v7 with: name: package-candidate path: ${{ runner.temp }}/package/ if-no-files-found: error retention-days: 1 unit: name: unit (${{ matrix.os }}, Node ${{ matrix.node }}) runs-on: ${{ matrix.os }} timeout-minutes: 20 permissions: contents: read strategy: fail-fast: false matrix: include: - os: ubuntu-latest node: 22.13.0 - os: ubuntu-latest node: 22 - os: ubuntu-latest node: 24 - os: ubuntu-latest node: 26 - os: macos-latest node: 24 - os: windows-latest node: 24 steps: - uses: actions/checkout@v6 with: persist-credentials: false - uses: actions/setup-node@v6 with: node-version: ${{ matrix.node }} cache: npm - run: npm ci - run: npm run test:unit integration-e2e: name: integration-e2e (${{ matrix.os }}, Node ${{ matrix.node }}) runs-on: ${{ matrix.os }} timeout-minutes: 30 permissions: contents: read strategy: fail-fast: false matrix: include: - os: ubuntu-latest node: 22.13.0 - os: ubuntu-latest node: 24 - os: macos-latest node: 24 - os: windows-latest node: 24 steps: - uses: actions/checkout@v6 with: persist-credentials: false - uses: actions/setup-node@v6 with: node-version: ${{ matrix.node }} cache: npm - run: npm ci - run: npm run test:integration - run: npm run test:e2e package-consumer: name: package-consumer (${{ matrix.os }}) runs-on: ${{ matrix.os }} timeout-minutes: 25 permissions: contents: read strategy: fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] steps: - uses: actions/checkout@v6 with: persist-credentials: false - uses: actions/setup-node@v6 with: node-version: 24 cache: npm - run: npm ci - run: npm run test:package local-model-smoke: name: local model smoke (${{ matrix.model }}, ${{ matrix.os }}) needs: package-candidate runs-on: ${{ matrix.os }} timeout-minutes: 60 permissions: contents: read strategy: fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] model: - local/potion-code-16m-v2 - local/all-minilm-l6-v2 - local/embeddinggemma-300m steps: - uses: actions/checkout@v6 with: persist-credentials: false - uses: actions/setup-node@v6 with: node-version: 24 cache: npm - uses: actions/download-artifact@v8 with: name: package-candidate path: ${{ runner.temp }}/package - name: Restore the real model cache uses: actions/cache@v5 with: path: ${{ runner.temp }}/zvec-grep-model-cache key: >- local-model-v2-${{ runner.os }}-${{ runner.arch }}-${{ matrix.model }}-${{ hashFiles('src/engine/models/catalog.ts', 'package-lock.json') }} - name: Run the packed package with real local embeddings env: ZVEC_GREP_PACKAGE_TARBALL: ${{ runner.temp }}/package ZVEC_GREP_MODEL_CACHE: ${{ runner.temp }}/zvec-grep-model-cache ZVEC_GREP_SMOKE_MODEL: ${{ matrix.model }} run: npm run test:smoke:local-model isolated-install: name: isolated-install runs-on: ubuntu-latest timeout-minutes: 20 permissions: contents: read steps: - uses: actions/checkout@v6 with: persist-credentials: false - uses: actions/setup-node@v6 with: node-version: 24 cache: npm - run: npm ci --install-strategy=nested - run: npm run build - run: npm run test:unit