feat: Claude Code Monitor — lanes, pipelines and a merged workspace
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.
This commit is contained in:
@@ -0,0 +1,323 @@
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# GitLab CI/CD Pipeline – Claude Code Agent Monitor
|
||||
#
|
||||
# Stages: test → build → deploy-staging → deploy-production
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
# ── Global settings ─────────────────────────────────────────────────────────
|
||||
default:
|
||||
image: node:22-alpine
|
||||
interruptible: true
|
||||
retry:
|
||||
max: 1
|
||||
when:
|
||||
- runner_system_failure
|
||||
- stuck_or_timeout_failure
|
||||
|
||||
variables:
|
||||
NODE_VERSION: "22"
|
||||
REGISTRY: "${CI_REGISTRY}"
|
||||
IMAGE_NAME: "${CI_REGISTRY_IMAGE}/agent-monitor"
|
||||
MCP_IMAGE_NAME: "${CI_REGISTRY_IMAGE}/agent-monitor-mcp"
|
||||
HELM_CHART_PATH: "deployments/helm/agent-monitor"
|
||||
APP_NAME: "agent-monitor"
|
||||
# Kaniko cache
|
||||
KANIKO_CACHE_ARGS: "--cache=true --cache-repo=${CI_REGISTRY_IMAGE}/cache"
|
||||
|
||||
stages:
|
||||
- test
|
||||
- build
|
||||
- deploy-staging
|
||||
- deploy-production
|
||||
- rollback
|
||||
|
||||
# ── Cache configuration ────────────────────────────────────────────────────
|
||||
.node_cache: &node_cache
|
||||
cache:
|
||||
key:
|
||||
files:
|
||||
- package-lock.json
|
||||
paths:
|
||||
- node_modules/
|
||||
policy: pull-push
|
||||
|
||||
# ── Test stage ──────────────────────────────────────────────────────────────
|
||||
lint:
|
||||
stage: test
|
||||
<<: *node_cache
|
||||
script:
|
||||
- npm ci --prefer-offline
|
||||
- npm run format:check
|
||||
rules:
|
||||
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
||||
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
||||
|
||||
test:server:
|
||||
stage: test
|
||||
<<: *node_cache
|
||||
script:
|
||||
- npm ci --prefer-offline
|
||||
- npm run test:server
|
||||
rules:
|
||||
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
||||
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
||||
artifacts:
|
||||
when: on_failure
|
||||
paths:
|
||||
- server/__tests__/
|
||||
expire_in: 7 days
|
||||
|
||||
test:client:
|
||||
stage: test
|
||||
<<: *node_cache
|
||||
script:
|
||||
- npm ci --prefer-offline
|
||||
- cd client && npm ci --prefer-offline
|
||||
- npm run test:client
|
||||
cache:
|
||||
key:
|
||||
files:
|
||||
- client/package-lock.json
|
||||
paths:
|
||||
- client/node_modules/
|
||||
policy: pull-push
|
||||
rules:
|
||||
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
||||
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
||||
|
||||
test:mcp:
|
||||
stage: test
|
||||
<<: *node_cache
|
||||
script:
|
||||
- npm run mcp:install
|
||||
- npm run mcp:typecheck
|
||||
- npm run mcp:test
|
||||
rules:
|
||||
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
||||
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
||||
|
||||
# ── Build stage ─────────────────────────────────────────────────────────────
|
||||
.kaniko_build: &kaniko_build
|
||||
stage: build
|
||||
image:
|
||||
name: gcr.io/kaniko-project/executor:v1.22.0-debug
|
||||
entrypoint: [""]
|
||||
before_script:
|
||||
- mkdir -p /kaniko/.docker
|
||||
- echo "{\"auths\":{\"${CI_REGISTRY}\":{\"auth\":\"$(printf "%s:%s" "${CI_REGISTRY_USER}" "${CI_REGISTRY_PASSWORD}" | base64)\"}}}" > /kaniko/.docker/config.json
|
||||
|
||||
build:app:
|
||||
<<: *kaniko_build
|
||||
script:
|
||||
- >-
|
||||
/kaniko/executor
|
||||
--context "${CI_PROJECT_DIR}"
|
||||
--dockerfile "${CI_PROJECT_DIR}/Dockerfile"
|
||||
--destination "${IMAGE_NAME}:${CI_COMMIT_SHORT_SHA}"
|
||||
--destination "${IMAGE_NAME}:${CI_COMMIT_REF_SLUG}"
|
||||
--destination "${IMAGE_NAME}:latest"
|
||||
${KANIKO_CACHE_ARGS}
|
||||
--label "org.opencontainers.image.revision=${CI_COMMIT_SHA}"
|
||||
--label "org.opencontainers.image.created=$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||
--label "org.opencontainers.image.source=${CI_PROJECT_URL}"
|
||||
rules:
|
||||
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
||||
- if: $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/
|
||||
|
||||
build:mcp:
|
||||
<<: *kaniko_build
|
||||
script:
|
||||
- >-
|
||||
/kaniko/executor
|
||||
--context "${CI_PROJECT_DIR}"
|
||||
--dockerfile "${CI_PROJECT_DIR}/mcp/Dockerfile"
|
||||
--destination "${MCP_IMAGE_NAME}:${CI_COMMIT_SHORT_SHA}"
|
||||
--destination "${MCP_IMAGE_NAME}:${CI_COMMIT_REF_SLUG}"
|
||||
--destination "${MCP_IMAGE_NAME}:latest"
|
||||
${KANIKO_CACHE_ARGS}
|
||||
rules:
|
||||
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
||||
- if: $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/
|
||||
|
||||
# Security scan
|
||||
security:scan:
|
||||
stage: build
|
||||
needs: ["build:app", "build:mcp"]
|
||||
image:
|
||||
name: aquasec/trivy:latest
|
||||
entrypoint: [""]
|
||||
script:
|
||||
- trivy image --exit-code 1 --severity HIGH,CRITICAL --format table "${IMAGE_NAME}:${CI_COMMIT_SHORT_SHA}"
|
||||
- trivy image --exit-code 1 --severity HIGH,CRITICAL --format table "${MCP_IMAGE_NAME}:${CI_COMMIT_SHORT_SHA}"
|
||||
- trivy image --exit-code 1 --severity CRITICAL --format json --output trivy-app-report.json "${IMAGE_NAME}:${CI_COMMIT_SHORT_SHA}"
|
||||
- trivy image --exit-code 1 --severity CRITICAL --format json --output trivy-mcp-report.json "${MCP_IMAGE_NAME}:${CI_COMMIT_SHORT_SHA}"
|
||||
artifacts:
|
||||
paths:
|
||||
- trivy-app-report.json
|
||||
- trivy-mcp-report.json
|
||||
expire_in: 30 days
|
||||
allow_failure: false
|
||||
rules:
|
||||
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
||||
- if: $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/
|
||||
|
||||
# ── Deploy Staging ──────────────────────────────────────────────────────────
|
||||
deploy:staging:
|
||||
stage: deploy-staging
|
||||
image:
|
||||
name: alpine/helm:3.14.0
|
||||
entrypoint: [""]
|
||||
needs:
|
||||
- build:app
|
||||
- build:mcp
|
||||
environment:
|
||||
name: staging
|
||||
url: https://staging.agent-monitor.example.com
|
||||
on_stop: stop:staging
|
||||
before_script:
|
||||
- apk add --no-cache curl aws-cli kubectl
|
||||
- aws eks update-kubeconfig --region "${AWS_REGION:-us-west-2}" --name "${EKS_CLUSTER_STAGING:-agent-monitor-staging}"
|
||||
script:
|
||||
- |
|
||||
helm upgrade --install ${APP_NAME} ${HELM_CHART_PATH} \
|
||||
--namespace agent-monitor-staging \
|
||||
--create-namespace \
|
||||
--set image.repository=${IMAGE_NAME} \
|
||||
--set image.tag=${CI_COMMIT_SHORT_SHA} \
|
||||
--set mcp.image.repository=${MCP_IMAGE_NAME} \
|
||||
--set mcp.image.tag=${CI_COMMIT_SHORT_SHA} \
|
||||
--set environment=staging \
|
||||
--values ${HELM_CHART_PATH}/values-staging.yaml \
|
||||
--wait \
|
||||
--atomic \
|
||||
--timeout 600s
|
||||
- |
|
||||
echo "Running health check..."
|
||||
kubectl wait --for=condition=ready pod \
|
||||
-l app.kubernetes.io/name=${APP_NAME} \
|
||||
-n agent-monitor-staging \
|
||||
--timeout=300s
|
||||
echo "✔ Staging deployment successful"
|
||||
rules:
|
||||
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
||||
- if: $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/
|
||||
|
||||
stop:staging:
|
||||
stage: deploy-staging
|
||||
image:
|
||||
name: alpine/helm:3.14.0
|
||||
entrypoint: [""]
|
||||
environment:
|
||||
name: staging
|
||||
action: stop
|
||||
before_script:
|
||||
- apk add --no-cache aws-cli kubectl
|
||||
- aws eks update-kubeconfig --region "${AWS_REGION:-us-west-2}" --name "${EKS_CLUSTER_STAGING:-agent-monitor-staging}"
|
||||
script:
|
||||
- helm uninstall ${APP_NAME} -n agent-monitor-staging --wait || true
|
||||
when: manual
|
||||
rules:
|
||||
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
||||
|
||||
# ── Deploy Production ───────────────────────────────────────────────────────
|
||||
deploy:production:
|
||||
stage: deploy-production
|
||||
image:
|
||||
name: alpine/helm:3.14.0
|
||||
entrypoint: [""]
|
||||
needs:
|
||||
- deploy:staging
|
||||
environment:
|
||||
name: production
|
||||
url: https://agent-monitor.example.com
|
||||
before_script:
|
||||
- apk add --no-cache curl aws-cli kubectl
|
||||
- aws eks update-kubeconfig --region "${AWS_REGION:-us-west-2}" --name "${EKS_CLUSTER_PRODUCTION:-agent-monitor-production}"
|
||||
script:
|
||||
# Pre-deploy backup
|
||||
- |
|
||||
POD=$(kubectl get pods -n agent-monitor-production \
|
||||
-l app.kubernetes.io/name=${APP_NAME} \
|
||||
--field-selector=status.phase=Running \
|
||||
-o jsonpath='{.items[0].metadata.name}' 2>/dev/null || echo "")
|
||||
if [ -n "$POD" ]; then
|
||||
echo "Creating pre-deploy backup..."
|
||||
kubectl exec "$POD" -n agent-monitor-production -- \
|
||||
sh -c "cp /app/data/dashboard.db /tmp/pre-deploy-backup.db" 2>/dev/null || true
|
||||
fi
|
||||
# Deploy
|
||||
- |
|
||||
helm upgrade --install ${APP_NAME} ${HELM_CHART_PATH} \
|
||||
--namespace agent-monitor-production \
|
||||
--create-namespace \
|
||||
--set image.repository=${IMAGE_NAME} \
|
||||
--set image.tag=${CI_COMMIT_SHORT_SHA} \
|
||||
--set mcp.image.repository=${MCP_IMAGE_NAME} \
|
||||
--set mcp.image.tag=${CI_COMMIT_SHORT_SHA} \
|
||||
--set environment=production \
|
||||
--values ${HELM_CHART_PATH}/values-production.yaml \
|
||||
--wait \
|
||||
--atomic \
|
||||
--timeout 600s
|
||||
# Health check
|
||||
- |
|
||||
echo "Running production health check..."
|
||||
kubectl wait --for=condition=ready pod \
|
||||
-l app.kubernetes.io/name=${APP_NAME} \
|
||||
-n agent-monitor-production \
|
||||
--timeout=300s
|
||||
echo "✔ Production deployment successful"
|
||||
when: manual
|
||||
allow_failure: false
|
||||
rules:
|
||||
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
||||
- if: $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/
|
||||
|
||||
# ── Rollback ────────────────────────────────────────────────────────────────
|
||||
rollback:staging:
|
||||
stage: rollback
|
||||
image:
|
||||
name: alpine/helm:3.14.0
|
||||
entrypoint: [""]
|
||||
environment:
|
||||
name: staging
|
||||
before_script:
|
||||
- apk add --no-cache aws-cli kubectl
|
||||
- aws eks update-kubeconfig --region "${AWS_REGION:-us-west-2}" --name "${EKS_CLUSTER_STAGING:-agent-monitor-staging}"
|
||||
script:
|
||||
- echo "Rolling back staging..."
|
||||
- helm rollback ${APP_NAME} ${ROLLBACK_REVISION:-0} -n agent-monitor-staging --wait --timeout 300s
|
||||
- |
|
||||
kubectl wait --for=condition=ready pod \
|
||||
-l app.kubernetes.io/name=${APP_NAME} \
|
||||
-n agent-monitor-staging \
|
||||
--timeout=300s
|
||||
- echo "✔ Staging rollback complete"
|
||||
when: manual
|
||||
rules:
|
||||
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
||||
|
||||
rollback:production:
|
||||
stage: rollback
|
||||
image:
|
||||
name: alpine/helm:3.14.0
|
||||
entrypoint: [""]
|
||||
environment:
|
||||
name: production
|
||||
before_script:
|
||||
- apk add --no-cache aws-cli kubectl
|
||||
- aws eks update-kubeconfig --region "${AWS_REGION:-us-west-2}" --name "${EKS_CLUSTER_PRODUCTION:-agent-monitor-production}"
|
||||
script:
|
||||
- echo "⚠ Rolling back PRODUCTION..."
|
||||
- helm history ${APP_NAME} -n agent-monitor-production --max 5
|
||||
- helm rollback ${APP_NAME} ${ROLLBACK_REVISION:-0} -n agent-monitor-production --wait --timeout 300s
|
||||
- |
|
||||
kubectl wait --for=condition=ready pod \
|
||||
-l app.kubernetes.io/name=${APP_NAME} \
|
||||
-n agent-monitor-production \
|
||||
--timeout=300s
|
||||
- echo "✔ Production rollback complete"
|
||||
when: manual
|
||||
allow_failure: false
|
||||
rules:
|
||||
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
||||
Reference in New Issue
Block a user