57dc91585d
Internal SmartGift build of a Claude Code monitoring dashboard. Lanes: a durable unit of parallel agent work, one per working directory, tracked across session restarts. Managed lanes are git worktrees the dashboard provisions and can reset or remove behind a three-check destroy guard and a counted preflight; adopted lanes are directories you already own and are never destroyable. Pipelines: a lane moves through pipeline stages. A stage the agent declares with evidence renders green; a stage inferred from the tool-event stream renders dashed amber and never counts as done. Detection is forward-only within a 30-minute window, and never writes the declared stage. Workspace: one page at /run with a lane grid, the selected lane's pipeline, and a full Claude console behind a disclosure.
251 lines
8.0 KiB
YAML
251 lines
8.0 KiB
YAML
# ─────────────────────────────────────────────────────────────────────────────
|
||
# 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"
|