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:
2026-07-29 17:07:45 +07:00
commit 57dc91585d
783 changed files with 221743 additions and 0 deletions
+250
View File
@@ -0,0 +1,250 @@
# ─────────────────────────────────────────────────────────────────────────────
# 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"
+335
View File
@@ -0,0 +1,335 @@
# ─────────────────────────────────────────────────────────────────────────────
# GitHub Actions Deploy Pipeline Claude Code Agent Monitor
#
# Triggers on version tags and manual dispatch. Deploys to staging
# automatically and to production after manual approval.
# ─────────────────────────────────────────────────────────────────────────────
name: Deploy
on:
push:
tags:
- "v*"
workflow_dispatch:
inputs:
environment:
description: "Target environment"
required: true
type: choice
options:
- staging
- production
image_tag:
description: "Image tag to deploy (default: latest from main)"
required: false
type: string
permissions:
contents: read
packages: read
id-token: write # For OIDC cloud auth
concurrency:
group: deploy-${{ github.event.inputs.environment || 'staging' }}
cancel-in-progress: false
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}/agent-monitor
MCP_IMAGE_NAME: ${{ github.repository }}/agent-monitor-mcp
HELM_CHART_PATH: deployments/helm/agent-monitor
jobs:
# ── Resolve image tag ──────────────────────────────────────────────────
prepare:
name: Prepare Deployment
runs-on: ubuntu-latest
outputs:
image-tag: ${{ steps.resolve.outputs.tag }}
version: ${{ steps.resolve.outputs.version }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Resolve image tag
id: resolve
run: |
if [[ -n "${{ github.event.inputs.image_tag }}" ]]; then
TAG="${{ github.event.inputs.image_tag }}"
elif [[ "${{ github.ref_type }}" == "tag" ]]; then
TAG="${{ github.ref_name }}"
else
TAG="sha-$(git rev-parse --short HEAD)"
fi
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
echo "version=${TAG#v}" >> "$GITHUB_OUTPUT"
echo "Resolved image tag: ${TAG}"
# ── Build (if triggered by tag) ────────────────────────────────────────
build:
name: Build Images
runs-on: ubuntu-latest
needs: prepare
if: github.ref_type == 'tag'
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: Build & push app image
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile
push: true
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.prepare.outputs.image-tag }}
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
cache-from: type=gha
cache-to: type=gha,mode=max
platforms: linux/amd64,linux/arm64
- name: Build & push MCP image
uses: docker/build-push-action@v5
with:
context: .
file: ./mcp/Dockerfile
push: true
tags: |
${{ env.REGISTRY }}/${{ env.MCP_IMAGE_NAME }}:${{ needs.prepare.outputs.image-tag }}
${{ env.REGISTRY }}/${{ env.MCP_IMAGE_NAME }}:latest
cache-from: type=gha
cache-to: type=gha,mode=max
platforms: linux/amd64,linux/arm64
# ── Deploy to Staging ──────────────────────────────────────────────────
deploy-staging:
name: Deploy to Staging
runs-on: ubuntu-latest
needs: [prepare, build]
if: |
always() &&
needs.prepare.result == 'success' &&
(needs.build.result == 'success' || needs.build.result == 'skipped') &&
(github.event.inputs.environment == 'staging' || github.event.inputs.environment == '' || github.ref_type == 'tag')
environment:
name: staging
url: https://staging.agent-monitor.example.com
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Configure AWS credentials (OIDC)
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE_ARN_STAGING }}
aws-region: ${{ vars.AWS_REGION || 'us-west-2' }}
- name: Setup kubectl
uses: azure/setup-kubectl@v3
with:
version: "v1.29.0"
- name: Setup Helm
uses: azure/setup-helm@v4
with:
version: "v3.14.0"
- name: Update kubeconfig
run: |
aws eks update-kubeconfig \
--region ${{ vars.AWS_REGION || 'us-west-2' }} \
--name ${{ vars.EKS_CLUSTER_STAGING || 'agent-monitor-staging' }}
- name: Deploy to staging via Helm
run: |
helm upgrade --install agent-monitor ${{ env.HELM_CHART_PATH }} \
--namespace agent-monitor-staging \
--create-namespace \
--set image.repository=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} \
--set image.tag=${{ needs.prepare.outputs.image-tag }} \
--set mcp.image.repository=${{ env.REGISTRY }}/${{ env.MCP_IMAGE_NAME }} \
--set mcp.image.tag=${{ needs.prepare.outputs.image-tag }} \
--set environment=staging \
--set ingress.host=staging.agent-monitor.example.com \
--values ${{ env.HELM_CHART_PATH }}/values-staging.yaml \
--wait \
--atomic \
--timeout 600s
- name: Health check
run: |
echo "Waiting for pods to be ready..."
kubectl wait --for=condition=ready pod \
-l app.kubernetes.io/name=agent-monitor \
-n agent-monitor-staging \
--timeout=300s
# Port forward and check health
kubectl port-forward svc/agent-monitor 14820:4820 -n agent-monitor-staging &
PF_PID=$!
sleep 5
for i in $(seq 1 10); do
if curl -sf http://localhost:14820/api/health | grep -q '"status":"ok"'; then
echo "✔ Health check passed"
kill $PF_PID 2>/dev/null || true
exit 0
fi
echo "Attempt $i/10..."
sleep 5
done
kill $PF_PID 2>/dev/null || true
echo "✖ Health check failed"
exit 1
- name: Notify Slack (staging)
if: always()
uses: slackapi/slack-github-action@v1.26.0
with:
payload: |
{
"text": "${{ job.status == 'success' && '✅' || '❌' }} Staging deployment ${{ job.status }}: `${{ needs.prepare.outputs.image-tag }}`",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "${{ job.status == 'success' && ':white_check_mark:' || ':x:' }} *Staging Deployment ${{ job.status }}*\n*Image:* `${{ needs.prepare.outputs.image-tag }}`\n*Commit:* `${{ github.sha }}`\n*Actor:* ${{ github.actor }}"
}
}
]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
# ── Deploy to Production ───────────────────────────────────────────────
deploy-production:
name: Deploy to Production
runs-on: ubuntu-latest
needs: [prepare, deploy-staging]
if: |
always() &&
needs.prepare.result == 'success' &&
needs.deploy-staging.result == 'success' &&
(github.event.inputs.environment == 'production' || github.ref_type == 'tag')
environment:
name: production
url: https://agent-monitor.example.com
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Configure AWS credentials (OIDC)
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE_ARN_PRODUCTION }}
aws-region: ${{ vars.AWS_REGION || 'us-west-2' }}
- name: Setup kubectl
uses: azure/setup-kubectl@v3
with:
version: "v1.29.0"
- name: Setup Helm
uses: azure/setup-helm@v4
with:
version: "v3.14.0"
- name: Update kubeconfig
run: |
aws eks update-kubeconfig \
--region ${{ vars.AWS_REGION || 'us-west-2' }} \
--name ${{ vars.EKS_CLUSTER_PRODUCTION || 'agent-monitor-production' }}
- name: Create database backup
run: |
chmod +x deployments/scripts/db-backup.sh
# Find a running pod to backup from
POD=$(kubectl get pods -n agent-monitor-production \
-l app.kubernetes.io/name=agent-monitor \
--field-selector=status.phase=Running \
-o jsonpath='{.items[0].metadata.name}' 2>/dev/null || echo "")
if [[ -n "$POD" ]]; then
echo "Backing up database from pod: $POD"
kubectl exec "$POD" -n agent-monitor-production -- \
sh -c "cp /app/data/dashboard.db /tmp/pre-deploy-backup.db 2>/dev/null || true"
echo "Pre-deploy backup created"
else
echo "⚠ No running pods found skipping backup"
fi
- name: Deploy to production via Helm
run: |
helm upgrade --install agent-monitor ${{ env.HELM_CHART_PATH }} \
--namespace agent-monitor-production \
--create-namespace \
--set image.repository=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} \
--set image.tag=${{ needs.prepare.outputs.image-tag }} \
--set mcp.image.repository=${{ env.REGISTRY }}/${{ env.MCP_IMAGE_NAME }} \
--set mcp.image.tag=${{ needs.prepare.outputs.image-tag }} \
--set environment=production \
--set ingress.host=agent-monitor.example.com \
--values ${{ env.HELM_CHART_PATH }}/values-production.yaml \
--wait \
--atomic \
--timeout 600s
- name: Health check
run: |
echo "Waiting for pods to be ready..."
kubectl wait --for=condition=ready pod \
-l app.kubernetes.io/name=agent-monitor \
-n agent-monitor-production \
--timeout=300s
kubectl port-forward svc/agent-monitor 14820:4820 -n agent-monitor-production &
PF_PID=$!
sleep 5
for i in $(seq 1 15); do
if curl -sf http://localhost:14820/api/health | grep -q '"status":"ok"'; then
echo "✔ Production health check passed"
kill $PF_PID 2>/dev/null || true
exit 0
fi
echo "Attempt $i/15..."
sleep 5
done
kill $PF_PID 2>/dev/null || true
echo "✖ Production health check failed!"
exit 1
- name: Notify Slack (production)
if: always()
uses: slackapi/slack-github-action@v1.26.0
with:
payload: |
{
"text": "${{ job.status == 'success' && '🚀' || '🚨' }} Production deployment ${{ job.status }}: `${{ needs.prepare.outputs.image-tag }}`",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "${{ job.status == 'success' && ':rocket:' || ':rotating_light:' }} *Production Deployment ${{ job.status }}*\n*Image:* `${{ needs.prepare.outputs.image-tag }}`\n*Version:* `${{ needs.prepare.outputs.version }}`\n*Commit:* `${{ github.sha }}`\n*Actor:* ${{ github.actor }}\n*Workflow:* <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View Run>"
}
}
]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
+160
View File
@@ -0,0 +1,160 @@
# ─────────────────────────────────────────────────────────────────────────────
# GitHub Actions Rollback Pipeline Claude Code Agent Monitor
#
# Manual workflow to roll back a Helm deployment to a previous revision.
# ─────────────────────────────────────────────────────────────────────────────
name: Rollback
on:
workflow_dispatch:
inputs:
environment:
description: "Target environment to rollback"
required: true
type: choice
options:
- staging
- production
revision:
description: "Helm revision number (leave empty for previous)"
required: false
type: string
reason:
description: "Reason for rollback"
required: true
type: string
permissions:
contents: read
id-token: write
concurrency:
group: deploy-${{ github.event.inputs.environment }}
cancel-in-progress: false
jobs:
rollback:
name: Rollback ${{ github.event.inputs.environment }}
runs-on: ubuntu-latest
environment:
name: ${{ github.event.inputs.environment }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Configure AWS credentials (OIDC)
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ github.event.inputs.environment == 'production' && secrets.AWS_ROLE_ARN_PRODUCTION || secrets.AWS_ROLE_ARN_STAGING }}
aws-region: ${{ vars.AWS_REGION || 'us-west-2' }}
- name: Setup kubectl
uses: azure/setup-kubectl@v3
with:
version: "v1.29.0"
- name: Setup Helm
uses: azure/setup-helm@v4
with:
version: "v3.14.0"
- name: Update kubeconfig
run: |
CLUSTER_NAME="${{ github.event.inputs.environment == 'production' && vars.EKS_CLUSTER_PRODUCTION || vars.EKS_CLUSTER_STAGING }}"
CLUSTER_NAME="${CLUSTER_NAME:-agent-monitor-${{ github.event.inputs.environment }}}"
aws eks update-kubeconfig \
--region ${{ vars.AWS_REGION || 'us-west-2' }} \
--name "${CLUSTER_NAME}"
- name: Show Helm history
run: |
NAMESPACE="agent-monitor-${{ github.event.inputs.environment }}"
echo "## Current Helm History"
helm history agent-monitor -n "${NAMESPACE}" --max 10 || echo "No history found"
- name: Execute rollback
run: |
NAMESPACE="agent-monitor-${{ github.event.inputs.environment }}"
REVISION="${{ github.event.inputs.revision }}"
echo "Rolling back in namespace: ${NAMESPACE}"
ROLLBACK_ARGS="helm rollback agent-monitor"
if [[ -n "${REVISION}" ]]; then
ROLLBACK_ARGS="${ROLLBACK_ARGS} ${REVISION}"
echo "Target revision: ${REVISION}"
else
echo "Target revision: previous"
fi
${ROLLBACK_ARGS} -n "${NAMESPACE}" --wait --timeout 300s
echo "✔ Rollback command succeeded"
- name: Health check after rollback
run: |
NAMESPACE="agent-monitor-${{ github.event.inputs.environment }}"
echo "Waiting for pods to be ready..."
kubectl wait --for=condition=ready pod \
-l app.kubernetes.io/name=agent-monitor \
-n "${NAMESPACE}" \
--timeout=300s
kubectl port-forward svc/agent-monitor 14820:4820 -n "${NAMESPACE}" &
PF_PID=$!
sleep 5
HEALTHY=false
for i in $(seq 1 10); do
if curl -sf http://localhost:14820/api/health | grep -q '"status":"ok"'; then
echo "✔ Health check passed after rollback"
HEALTHY=true
break
fi
echo "Attempt $i/10..."
sleep 5
done
kill $PF_PID 2>/dev/null || true
if [[ "$HEALTHY" != true ]]; then
echo "✖ Health check failed after rollback!"
exit 1
fi
- name: Show post-rollback status
if: always()
run: |
NAMESPACE="agent-monitor-${{ github.event.inputs.environment }}"
echo "## Post-Rollback Status"
echo ""
echo "### Helm Status"
helm status agent-monitor -n "${NAMESPACE}" || true
echo ""
echo "### Pod Status"
kubectl get pods -n "${NAMESPACE}" -l app.kubernetes.io/name=agent-monitor || true
echo ""
echo "### Recent Events"
kubectl get events -n "${NAMESPACE}" --sort-by='.lastTimestamp' | tail -20 || true
- name: Notify Slack
if: always()
uses: slackapi/slack-github-action@v1.26.0
with:
payload: |
{
"text": "${{ job.status == 'success' && '⏪' || '🚨' }} Rollback ${{ job.status }} on ${{ github.event.inputs.environment }}",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "${{ job.status == 'success' && ':rewind:' || ':rotating_light:' }} *Rollback ${{ job.status }}*\n*Environment:* `${{ github.event.inputs.environment }}`\n*Revision:* `${{ github.event.inputs.revision || 'previous' }}`\n*Reason:* ${{ github.event.inputs.reason }}\n*Actor:* ${{ github.actor }}\n*Workflow:* <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View Run>"
}
}
]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK