# ───────────────────────────────────────────────────────────────────────────── # GitHub Actions CI Pipeline – Claude Code Agent Monitor # # Triggers on push to main and PRs. Runs linting, tests, builds Docker # images, and scans for security vulnerabilities. # ───────────────────────────────────────────────────────────────────────────── name: CI on: push: branches: [main] pull_request: branches: [main] permissions: contents: read packages: write security-events: write concurrency: group: ci-${{ github.ref }} cancel-in-progress: true env: NODE_VERSION: "22" REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }}/agent-monitor MCP_IMAGE_NAME: ${{ github.repository }}/agent-monitor-mcp jobs: # ── Lint & Format Check ───────────────────────────────────────────────── lint: name: Lint & Format runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} cache: "npm" - name: Install dependencies run: npm ci - name: Check formatting run: npm run format:check # ── Server Tests ──────────────────────────────────────────────────────── test-server: name: Server Tests runs-on: ubuntu-latest needs: lint steps: - name: Checkout code uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} cache: "npm" - name: Install dependencies run: npm ci - name: Run server tests run: npm run test:server # ── Client Tests ──────────────────────────────────────────────────────── test-client: name: Client Tests runs-on: ubuntu-latest needs: lint steps: - name: Checkout code uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} cache: "npm" - name: Install root dependencies run: npm ci - name: Install client dependencies run: cd client && npm ci - name: Run client tests run: npm run test:client # ── MCP Tests ─────────────────────────────────────────────────────────── test-mcp: name: MCP Sidecar Tests runs-on: ubuntu-latest needs: lint steps: - name: Checkout code uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} cache: "npm" - name: Install MCP dependencies run: npm run mcp:install - name: Type check MCP run: npm run mcp:typecheck - name: Run MCP tests run: npm run mcp:test # ── Build Docker Images ──────────────────────────────────────────────── build-image: name: Build Docker Images runs-on: ubuntu-latest needs: [test-server, test-client, test-mcp] # Only push images on main branch if: github.event_name == 'push' && github.ref == 'refs/heads/main' outputs: image-tag: ${{ steps.meta.outputs.version }} image-digest: ${{ steps.build-app.outputs.digest }} steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Login to Container Registry uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Extract metadata (app) id: meta uses: docker/metadata-action@v5 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} tags: | type=ref,event=branch type=sha,prefix=sha- type=raw,value=latest,enable={{is_default_branch}} type=semver,pattern={{version}},enable=${{ startsWith(github.ref, 'refs/tags/v') }} - name: Build & push app image id: build-app uses: docker/build-push-action@v5 with: context: . file: ./Dockerfile push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max platforms: linux/amd64,linux/arm64 - name: Extract metadata (MCP) id: meta-mcp uses: docker/metadata-action@v5 with: images: ${{ env.REGISTRY }}/${{ env.MCP_IMAGE_NAME }} tags: | type=ref,event=branch type=sha,prefix=sha- type=raw,value=latest,enable={{is_default_branch}} - name: Build & push MCP image id: build-mcp uses: docker/build-push-action@v5 with: context: . file: ./mcp/Dockerfile push: true tags: ${{ steps.meta-mcp.outputs.tags }} labels: ${{ steps.meta-mcp.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max platforms: linux/amd64,linux/arm64 # ── Security Scan ────────────────────────────────────────────────────── security-scan: name: Security Scan runs-on: ubuntu-latest needs: build-image if: github.event_name == 'push' && github.ref == 'refs/heads/main' steps: - name: Checkout code uses: actions/checkout@v4 - name: Login to Container Registry uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Run Trivy vulnerability scanner (app) uses: aquasecurity/trivy-action@master with: image-ref: "${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.build-image.outputs.image-tag }}" format: "sarif" output: "trivy-app-results.sarif" severity: "CRITICAL,HIGH" exit-code: "1" - name: Run Trivy vulnerability scanner (MCP) uses: aquasecurity/trivy-action@master with: image-ref: "${{ env.REGISTRY }}/${{ env.MCP_IMAGE_NAME }}:${{ needs.build-image.outputs.image-tag }}" format: "sarif" output: "trivy-mcp-results.sarif" severity: "CRITICAL,HIGH" exit-code: "1" - name: Upload Trivy SARIF (app) uses: github/codeql-action/upload-sarif@v3 if: always() with: sarif_file: "trivy-app-results.sarif" category: "trivy-app" - name: Upload Trivy SARIF (MCP) uses: github/codeql-action/upload-sarif@v3 if: always() with: sarif_file: "trivy-mcp-results.sarif" category: "trivy-mcp" - name: Run npm audit run: npm audit --production --audit-level=high - name: Trivy filesystem scan (IaC) uses: aquasecurity/trivy-action@master with: scan-type: "fs" scan-ref: "./deployments" format: "table" severity: "CRITICAL,HIGH" exit-code: "1"