Skip to content

CI CD Integration

Lisa edited this page Dec 26, 2025 · 23 revisions

CI/CD Integration

CKB becomes your single source of truth for change risk in CI/CD pipelines. Instead of treating code review as a human-only process, CKB provides precomputed analysis that travels with every PR:

  • Blast radius — Know exactly what breaks before merging
  • Suggested reviewers — Based on actual code ownership, not guesswork
  • Boundary detection — Flag when "local" changes cross API contracts
  • Risk scoring — Quantified risk based on hotspots, module spread, and history
  • Complexity gates — Block merges that exceed complexity thresholds
  • Coupling analysis — Warn when tightly-coupled files change independently
  • Dead code detection — Identify unused code from runtime telemetry
  • Health monitoring — Prometheus metrics and Kubernetes-ready probes
  • Risk audit — 8-factor risk scoring with quick wins recommendations
  • Eval suite — Regression testing for code intelligence quality

The pattern: run CLI commands and parse JSON output. (HTTP API also available for advanced use cases.)

What It Actually Does

When you call ckb pr-summary, CKB:

  1. Diffs the branches using git (base vs head)
  2. Maps files to modules based on path structure
  3. Checks each file against hotspot data — flags files with score > 0.5
  4. Queries ownership for each changed file (CODEOWNERS + git-blame)
  5. Calculates risk based on: file count, lines changed, hotspots touched, module spread
  6. Returns structured JSON with everything a PR comment needs

Example response shape:

{
  "summary": {
    "totalFiles": 12,
    "totalAdditions": 450,
    "totalDeletions": 120,
    "totalModules": 3,
    "hotspotsTouched": 2
  },
  "riskAssessment": {
    "level": "medium",
    "score": 0.45,
    "factors": ["Touches 2 hotspot(s)", "Spans 3 modules"],
    "suggestions": ["Extra review recommended for hotspot files"]
  },
  "suggestedReviewers": [
    {"owner": "@api-team", "reason": "Owns 8 of 12 changed files", "coverage": 0.67}
  ],
  "modulesAffected": [
    {"moduleId": "internal/api", "filesChanged": 8, "riskLevel": "medium"}
  ]
}

How It Works in GitHub Actions

CKB runs as CLI commands inside the runner. The workflow:

  1. Checkout with full history (fetch-depth: 0)
  2. Install CKB via npm or binary download
  3. Initialize: ckb init
  4. Run analysis: ckb pr-summary --base=origin/main
  5. Parse JSON output, post PR comment

Installation in CI

npm (Recommended):

- uses: actions/setup-node@v4
  with:
    node-version: '20'

- name: Install CKB
  run: npm install -g @tastehub/ckb

Or use npx (no install):

- uses: actions/setup-node@v4
  with:
    node-version: '20'

- name: Initialize CKB
  run: npx @tastehub/ckb init

Or download pre-built binary:

- run: |
    curl -sSL https://github.com/SimplyLiz/CodeMCP/releases/latest/download/ckb_linux_amd64.tar.gz | tar xz
    sudo mv ckb /usr/local/bin/

Or build from source:

- uses: actions/setup-go@v5
  with:
    go-version: '1.21'

- name: Install CKB
  run: |
    git clone https://github.com/SimplyLiz/CodeMCP.git /tmp/codemcp
    cd /tmp/codemcp
    go build -o /usr/local/bin/ckb ./cmd/ckb

Critical: Full Git History

CKB needs git history for ownership analysis. Without it, suggested reviewers will be empty.

- uses: actions/checkout@v4
  with:
    fetch-depth: 0  # Required

Environment Validation (v7.2)

Before running CKB analysis, validate that your CI environment has the required tooling installed. The ckb doctor --tier command checks for language-specific tools and outputs JSON for scripting.

Validate CI Environment

- name: Validate CKB Environment
  run: |
    # Check if required tools are installed for standard tier
    RESULT=$(ckb doctor --tier=standard --format json)

    # Check for missing tools
    MISSING=$(echo "$RESULT" | jq '[.languages[] | select(.status == "missing")] | length')

    if [ "$MISSING" -gt 0 ]; then
      echo "::warning::Missing $MISSING language tools"
      echo "$RESULT" | jq '.languages[] | select(.status == "missing") | "\(.name): \(.installCmd)"'
    fi

Tier-Specific Validation

Different tiers require different tools:

Tier Tools Required Use Case
fast None (tree-sitter built-in) Quick PR checks
standard SCIP indexers (scip-go, scip-typescript, etc.) Full code intelligence
full Standard + LSP servers Maximum accuracy
- name: Check Standard Tier Readiness
  run: |
    # Fail if standard tier tools aren't available
    ckb doctor --tier=standard --format json | jq -e '.ready == true' || {
      echo "::error::CI environment missing required tools for standard tier"
      ckb doctor --tier=standard
      exit 1
    }

Install Missing Tools

- name: Setup Go Indexer
  run: |
    # Check if scip-go is needed and missing
    if ckb doctor --tier=standard --format json | jq -e '.languages[] | select(.name == "go" and .status == "missing")' > /dev/null 2>&1; then
      go install github.com/sourcegraph/scip-go/cmd/scip-go@latest
    fi

PR Analysis Workflow

# .github/workflows/pr-analysis.yml
name: CKB PR Analysis

on:
  pull_request:
    types: [opened, synchronize, reopened]

permissions:
  contents: read
  pull-requests: write

jobs:
  analyze:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - uses: actions/setup-node@v4
        with:
          node-version: '20'

      - name: Install CKB
        run: npm install -g @tastehub/ckb

      - name: Initialize CKB
        run: ckb init

      - name: Run PR Summary
        id: summary
        run: |
          # Run CLI command, output JSON to stdout
          ckb pr-summary --base=origin/${{ github.base_ref }} > pr-analysis.json 2>pr-analysis-err.txt || true

          RISK=$(jq -r '.riskAssessment.level // "unknown"' pr-analysis.json)
          echo "risk=$RISK" >> $GITHUB_OUTPUT

      - name: Post Comment
        uses: actions/github-script@v7
        with:
          script: |
            const fs = require('fs');
            const data = JSON.parse(fs.readFileSync('pr-analysis.json', 'utf8'));

            const emoji = {high: '🔴', medium: '🟡', low: '🟢'}[data.riskAssessment?.level] || '⚪';
            const s = data.summary || {};
            const risk = data.riskAssessment || {};

            let body = '## CKB PR Analysis ' + emoji + ' ' + (risk.level || 'unknown').toUpperCase() + '\n\n';
            body += '| Metric | Value |\n';
            body += '|--------|------:|\n';
            body += '| Files | ' + (s.totalFiles || 0) + ' |\n';
            body += '| Additions | +' + (s.totalAdditions || 0) + ' |\n';
            body += '| Deletions | -' + (s.totalDeletions || 0) + ' |\n';
            body += '| Hotspots | ' + (s.hotspotsTouched || 0) + ' |\n';

            if (risk.factors?.length) {
              body += '\n### Risk Factors\n';
              risk.factors.forEach(f => body += '- ' + f + '\n');
            }

            github.rest.issues.createComment({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: context.issue.number,
              body: body
            });

Incremental Indexing in CI (v7.3)

For Go projects, CKB supports incremental indexing that only processes changed files. This can dramatically speed up CI pipelines.

Why Use Incremental Indexing in CI?

Scenario Full Index Incremental
Large Go project (10k files) ~60s ~2-5s
Typical PR (5-10 files changed) ~60s ~1-2s
Single file hotfix ~60s <1s

The key insight: PR pipelines typically change a small fraction of files. Incremental indexing is O(changed files), not O(total files).

Basic Incremental Workflow

- name: Restore CKB Cache
  uses: actions/cache@v4
  with:
    path: .ckb/
    key: ckb-${{ runner.os }}-${{ hashFiles('go.sum') }}
    restore-keys: |
      ckb-${{ runner.os }}-

- name: Index (Incremental)
  run: |
    ckb init
    ckb index  # Incremental by default for Go

    # Show what was updated
    echo "Index complete"

When to Use --force

# Nightly: Full reindex for maximum accuracy
- name: Nightly Full Index
  if: github.event_name == 'schedule'
  run: ckb index --force

# PR: Incremental for speed
- name: PR Incremental Index
  if: github.event_name == 'pull_request'
  run: ckb index

Use --force for nightly builds when accuracy matters. Use incremental for PRs when speed matters. For PR analysis, incremental is usually sufficient—you care about what the changed code does, not what calls it.

See Incremental Indexing for accuracy guarantees, transitive invalidation modes, and troubleshooting.

Caching Strategy

For incremental indexing to work across CI runs, you must cache the .ckb/ directory:

- name: Restore CKB Cache
  uses: actions/cache@v4
  with:
    path: .ckb/
    key: ckb-${{ runner.os }}-${{ github.ref }}-${{ github.sha }}
    restore-keys: |
      ckb-${{ runner.os }}-${{ github.ref }}-
      ckb-${{ runner.os }}-

- name: Index
  run: |
    ckb init
    ckb index

- name: Save CKB Cache
  uses: actions/cache/save@v4
  if: always()
  with:
    path: .ckb/
    key: ckb-${{ runner.os }}-${{ github.ref }}-${{ github.sha }}

Cache key strategy:

  • Include github.sha for exact match on re-runs
  • Include github.ref to separate branch caches
  • Use restore-keys to fall back to older caches

Complete Incremental CI Workflow

# .github/workflows/ckb-incremental.yml
name: CKB Analysis (Incremental)

on:
  pull_request:
  push:
    branches: [main]

jobs:
  analyze:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - uses: actions/setup-node@v4
        with:
          node-version: '20'

      - uses: actions/setup-go@v5
        with:
          go-version: '1.21'

      - name: Install Tools
        run: |
          npm install -g @tastehub/ckb
          go install github.com/sourcegraph/scip-go/cmd/scip-go@latest

      - name: Restore Cache
        uses: actions/cache@v4
        with:
          path: .ckb/
          key: ckb-${{ runner.os }}-${{ github.ref }}-${{ github.sha }}
          restore-keys: |
            ckb-${{ runner.os }}-${{ github.ref }}-
            ckb-${{ runner.os }}-

      - name: Index
        run: |
          ckb init
          # Use --force on main branch pushes for accuracy
          if [ "${{ github.event_name }}" = "push" ] && [ "${{ github.ref }}" = "refs/heads/main" ]; then
            ckb index --force
          else
            ckb index
          fi

      - name: Analyze
        run: |
          ckb serve --port 8080 &
          sleep 2
          curl -s http://localhost:8080/status | jq .

      - name: Save Cache
        uses: actions/cache/save@v4
        if: always()
        with:
          path: .ckb/
          key: ckb-${{ runner.os }}-${{ github.ref }}-${{ github.sha }}

Lock File Behavior

CKB uses a lock file (.ckb/index.lock) to prevent concurrent indexing. In CI:

  • If a previous step is still indexing, subsequent ckb index calls will wait
  • Lock is automatically released on process exit
  • Stale locks (from crashed processes) are detected and removed

This is safe for parallel jobs as long as they don't share the same .ckb/ directory.


Change Impact Analysis for CI (v7.5)

CKB's Change Impact Analysis maps git diffs to SCIP symbols, then uses the call graph to identify what downstream code might break. This enables selective test execution and automated PR comments.

PR Comment Widget (Simplest Integration)

Post impact analysis as a sticky PR comment with just 3 steps:

- uses: actions/checkout@v4
  with:
    fetch-depth: 0

- name: Generate Impact Report
  run: |
    ckb index --if-stale=24h
    ckb impact --range="${{ github.event.pull_request.base.sha }}..${{ github.sha }}" \
      --format=markdown > impact.md

- name: Post Comment
  uses: marocchino/sticky-pull-request-comment@v2
  with:
    header: ckb-impact
    path: impact.md

The --format=markdown flag generates PR-ready output. The sticky-pull-request-comment action updates the same comment on each push instead of creating duplicates.

Output Formats

Format Use Case
--format=json Machine-readable for scripting
--format=markdown PR comments, GitHub/GitLab integration
--format=text Human-readable terminal output (default)

ROI: Real CI Savings

Scenario Full Suite With Impact Analysis Savings
10,000 tests, 45 min 45 min ~5 min (typical PR) 89%
50 CI runs/day 37.5 hours ~4 hours $500+/day*

*Assuming $0.008/min for CI compute

See Impact-Analysis for full documentation including:

  • Complete GitHub Actions workflow with risk gates and reviewer assignment
  • Prerequisites (SCIP index, coverage files, CODEOWNERS)
  • Affected tests (ckb affected-tests)
  • Reviewer suggestions (ckb reviewers)
  • Pre-commit hooks
  • Confidence scoring and risk calculation

Scheduled Architecture Refresh

Run daily to keep architectural data fresh:

# .github/workflows/ckb-refresh.yml
name: CKB Architecture Refresh

on:
  schedule:
    - cron: '0 2 * * *'  # Daily at 2 AM UTC
  workflow_dispatch:
    inputs:
      force:
        description: 'Force full refresh'
        type: boolean
        default: false

permissions:
  contents: write

jobs:
  refresh:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - uses: actions/setup-node@v4
        with:
          node-version: '20'

      - name: Install CKB
        run: npm install -g @tastehub/ckb

      - name: Initialize and Start Server
        run: |
          ckb init
          ckb serve --port 8080 &
          sleep 2

      - name: Refresh Architecture
        run: |
          curl -s -X POST http://localhost:8080/architecture/refresh \
            -H "Content-Type: application/json" \
            -d '{"scope": "all", "force": ${{ inputs.force || false }}}'

      - name: Generate Reports
        run: |
          curl -s "http://localhost:8080/hotspots?limit=20" > hotspots.json
          curl -s "http://localhost:8080/ownership/drift?threshold=0.3" > drift.json

      - name: Upload Reports
        uses: actions/upload-artifact@v4
        with:
          name: ckb-reports
          path: '*.json'
          retention-days: 30

      - name: Cache CKB Database
        run: |
          git config user.email "github-actions[bot]@users.noreply.github.com"
          git config user.name "github-actions[bot]"
          git add .ckb/
          git diff --staged --quiet || git commit -m "chore: update CKB cache [skip ci]"
          git push

Using Async Refresh in CI

For large repositories, use async mode to avoid timeouts:

- name: Start CKB Server
  run: |
    ckb init
    ckb serve --port 8080 &
    sleep 2

- name: Start Async Refresh
  id: refresh
  run: |
    RESULT=$(curl -s -X POST http://localhost:8080/architecture/refresh \
      -H "Content-Type: application/json" \
      -d '{"scope": "all", "async": true}')
    JOB_ID=$(echo "$RESULT" | jq -r '.jobId')
    echo "job_id=$JOB_ID" >> $GITHUB_OUTPUT

- name: Wait for Completion
  run: |
    JOB_ID="${{ steps.refresh.outputs.job_id }}"
    while true; do
      STATUS=$(curl -s "http://localhost:8080/jobs/$JOB_ID")
      STATE=$(echo "$STATUS" | jq -r '.status')
      PROGRESS=$(echo "$STATUS" | jq -r '.progress')

      echo "Job $JOB_ID: $STATE ($PROGRESS%)"

      if [ "$STATE" = "completed" ]; then
        echo "$STATUS" | jq .result
        break
      elif [ "$STATE" = "failed" ]; then
        echo "Job failed: $(echo $STATUS | jq -r '.error')"
        exit 1
      fi

      sleep 5
    done

Available Interfaces

CKB provides two interfaces for CI/CD integration:

  1. CLI commands — Direct invocation, simpler setup, recommended for most use cases
  2. HTTP API — Start server first, useful for multiple queries or daemon mode

CLI Commands (Recommended)

These commands work directly without starting a server:

# PR analysis
ckb pr-summary --base=main --head=HEAD

# Complexity analysis
ckb complexity internal/query/engine.go

# Coupling analysis
ckb coupling --files=file1.go,file2.go --min-correlation=0.7

# Risk audit
ckb audit --min-score=60 --limit=20

# Dead code detection
ckb dead-code --threshold=0.9 --limit=20

# Hotspots
ckb hotspots --limit=20

# Ownership drift
ckb ownership --drift --threshold=0.3

HTTP API Endpoints

Start the server first: ckb serve --port 8080 &

POST /pr/summary — PR analysis

curl -s -X POST http://localhost:8080/pr/summary \
  -H "Content-Type: application/json" \
  -d '{"baseBranch": "main"}'

GET /ownership/drift — CODEOWNERS accuracy check

curl "http://localhost:8080/ownership/drift?threshold=0.3&limit=10"

GET /hotspots — High-churn files

curl "http://localhost:8080/hotspots?limit=20"

POST /architecture/refresh — Rebuild cached analysis

curl -X POST http://localhost:8080/architecture/refresh \
  -H "Content-Type: application/json" \
  -d '{"scope": "all", "async": true}'

GET /jobs/:id — Check async job status

curl "http://localhost:8080/jobs/job-abc123"

GET /health — Liveness probe

curl "http://localhost:8080/health"

GET /ready — Readiness probe

curl "http://localhost:8080/ready"

GET /metrics — Prometheus metrics

curl "http://localhost:8080/metrics"

GET /telemetry/dead-code — Dead code candidates

curl "http://localhost:8080/telemetry/dead-code?threshold=0.9&limit=20"

GET /meta/languages — Language quality dashboard

curl "http://localhost:8080/meta/languages"

POST /cache/warm — Pre-warm cache

curl -X POST "http://localhost:8080/cache/warm" \
  -H "Content-Type: application/json" \
  -d '{"scope": "architecture"}'

POST /cache/clear — Clear cache

curl -X POST "http://localhost:8080/cache/clear"

CLI-Only Features

These features are only available via CLI, not HTTP API:

Feature CLI Command Notes
Complexity ckb complexity <file> Per-file cyclomatic/cognitive metrics
Coupling ckb coupling --files=... Co-change analysis
Risk Audit ckb audit 8-factor risk scoring
Diff Summary ckb diff-summary Summarize git ranges
Eval Suite ckb eval Quality regression testing

Contract-Aware CI (v6.3)

When changes touch API contracts (protobuf, OpenAPI), CKB can detect cross-boundary impact that simple file diffs miss.

Why This Matters

A "local" change to user.proto might break consumers in three other repositories. Without contract analysis, this surfaces as production incidents, not PR warnings.

Detecting Contract Changes

- name: Check Contract Impact
  run: |
    # Find changed contract files
    CONTRACTS=$(git diff --name-only ${{ github.base_ref }}...HEAD | grep -E '\.(proto|yaml|json)$' | grep -E '(proto/|openapi/|api/)' || true)

    if [ -n "$CONTRACTS" ]; then
      echo "⚠️ Contract files changed:"
      echo "$CONTRACTS"

      for CONTRACT in $CONTRACTS; do
        echo "Analyzing impact of $CONTRACT..."
        curl -s "http://localhost:8080/contracts/impact?path=$CONTRACT" | jq .
      done
    fi

Contract Impact Response

{
  "contract": "proto/api/v1/user.proto",
  "visibility": "public",
  "consumers": [
    {"repo": "frontend", "confidence": "high", "files": ["src/api/user.ts"]},
    {"repo": "mobile-app", "confidence": "medium", "files": ["lib/api/user.dart"]}
  ],
  "riskLevel": "high",
  "riskFactors": [
    "Public contract with 2 known consumers",
    "Breaking field removal detected"
  ],
  "suggestion": "Coordinate with frontend and mobile-app teams before merging"
}

Blocking on Contract Changes

- name: Contract Safety Check
  run: |
    CONTRACTS=$(git diff --name-only ${{ github.base_ref }}...HEAD | grep -E '\.proto$' || true)

    for CONTRACT in $CONTRACTS; do
      RESULT=$(curl -s "http://localhost:8080/contracts/impact?path=$CONTRACT")
      RISK=$(echo "$RESULT" | jq -r '.riskLevel')
      CONSUMERS=$(echo "$RESULT" | jq -r '.consumers | length')

      if [ "$RISK" = "high" ] && [ "$CONSUMERS" -gt 0 ]; then
        echo "::error::Contract $CONTRACT has $CONSUMERS consumers and is high risk"
        echo "$RESULT" | jq .
        exit 1
      fi
    done

Safe Refactor Checklist

When CKB detects a risky change, you can generate a checklist for the PR:

- name: Generate Refactor Checklist
  if: steps.summary.outputs.risk == 'high'
  uses: actions/github-script@v7
  with:
    script: |
      const data = JSON.parse(`${{ steps.summary.outputs.result }}`);

      let checklist = `## Safe Refactor Checklist\n\n`;
      checklist += `This PR was flagged as **high risk**. Please verify:\n\n`;

      // Add affected modules
      if (data.modulesAffected?.length) {
        checklist += `### Modules Affected\n`;
        data.modulesAffected.forEach(m => {
          checklist += `- [ ] Reviewed changes in \`${m.moduleId}\` (${m.filesChanged} files)\n`;
        });
        checklist += `\n`;
      }

      // Add reviewers to notify
      if (data.suggestedReviewers?.length) {
        checklist += `### Required Sign-offs\n`;
        data.suggestedReviewers.forEach(r => {
          checklist += `- [ ] ${r.owner} approved (owns ${Math.round(r.coverage * 100)}% of changes)\n`;
        });
        checklist += `\n`;
      }

      // Add hotspot warnings
      if (data.summary?.hotspotsTouched > 0) {
        checklist += `### Hotspot Review\n`;
        checklist += `- [ ] Verified ${data.summary.hotspotsTouched} hotspot file(s) have adequate test coverage\n`;
        checklist += `- [ ] Confirmed changes don't increase complexity\n\n`;
      }

      checklist += `### Before Merge\n`;
      checklist += `- [ ] All tests passing\n`;
      checklist += `- [ ] No unintended breaking changes\n`;
      checklist += `- [ ] Documentation updated if needed\n`;

      github.rest.issues.createComment({
        owner: context.repo.owner,
        repo: context.repo.repo,
        issue_number: context.issue.number,
        body: checklist
      });

Documentation Coverage CI (v7.3)

CKB can enforce documentation coverage thresholds and detect stale symbol references in CI.

Why This Matters

Documentation that references deleted or renamed symbols becomes misleading. A function signature change can leave docs describing behavior that no longer exists.

Coverage Enforcement

Fail the build if documentation coverage drops below a threshold:

- name: Check Doc Coverage
  run: |
    # Initialize and index docs
    ckb init
    ckb docs index

    # Fail if coverage below 80%
    ckb docs coverage --fail-under=80

Exit codes:

  • 0: Coverage meets or exceeds threshold
  • 1: Coverage below threshold

Staleness Detection

Check for broken symbol references:

- name: Check Stale Docs
  run: |
    ckb docs index
    STALE=$(ckb docs stale --all --format json | jq '.totalStale')

    if [ "$STALE" -gt 0 ]; then
      echo "::warning::Found $STALE stale symbol references in documentation"
      ckb docs stale --all
    fi

Before Renaming Symbols

Find what docs will break before you rename:

- name: Check Docs Impact
  run: |
    SYMBOL="UserService.Authenticate"

    # Find docs referencing this symbol
    DOCS=$(ckb docs symbol "$SYMBOL" --format json | jq '.docs | length')

    if [ "$DOCS" -gt 0 ]; then
      echo "::warning::$DOCS docs reference $SYMBOL - update after rename"
      ckb docs symbol "$SYMBOL"
    fi

Complete Doc CI Workflow

# .github/workflows/doc-check.yml
name: Documentation Quality

on:
  pull_request:
    paths:
      - '**/*.md'
      - '**/*.go'
      - '**/*.ts'

jobs:
  check-docs:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - uses: actions/setup-node@v4
        with:
          node-version: '20'

      - name: Install CKB
        run: npm install -g @tastehub/ckb

      - name: Initialize
        run: |
          ckb init
          ckb index
          ckb docs index

      - name: Check Coverage
        run: ckb docs coverage --fail-under=70

      - name: Check Staleness
        run: |
          RESULT=$(ckb docs stale --all --format json)
          STALE=$(echo "$RESULT" | jq '.totalStale')

          if [ "$STALE" -gt 0 ]; then
            echo "## Stale Documentation References" >> $GITHUB_STEP_SUMMARY
            echo "" >> $GITHUB_STEP_SUMMARY
            echo "$RESULT" | jq -r '.reports[] | .stale[] | "- \(.docPath):\(.line): \(.rawText) (\(.reason))"' >> $GITHUB_STEP_SUMMARY
            exit 1
          fi

Health and Readiness Checks

For Kubernetes deployments or orchestration systems, CKB provides standard health endpoints.

Basic Health Check

- name: Start CKB Server
  run: |
    ckb serve --port 8080 &
    # Wait for server to be ready
    until curl -sf http://localhost:8080/ready; do
      echo "Waiting for CKB..."
      sleep 1
    done

Available Endpoints

Endpoint Purpose Response
GET /health Liveness probe {"status": "ok"}
GET /health/detailed Full component status Status of each subsystem
GET /ready Readiness probe 200 if ready, 503 if not

Kubernetes Probes

# In your k8s deployment
livenessProbe:
  httpGet:
    path: /health
    port: 8080
  initialDelaySeconds: 5
  periodSeconds: 10

readinessProbe:
  httpGet:
    path: /ready
    port: 8080
  initialDelaySeconds: 2
  periodSeconds: 5

Prometheus Metrics

CKB exports metrics in Prometheus format for monitoring and dashboards.

Collecting Metrics

- name: Collect CKB Metrics
  run: |
    curl -s http://localhost:8080/metrics > ckb-metrics.txt

    # Example: extract cache hit rate
    CACHE_HITS=$(grep 'ckb_cache_hits_total' ckb-metrics.txt | awk '{print $2}')
    echo "Cache hits: $CACHE_HITS"

Key Metrics

Metric Type Description
ckb_requests_total Counter Total API requests
ckb_request_duration_seconds Histogram Request latency
ckb_cache_hits_total Counter Cache hit count
ckb_symbols_indexed Gauge Number of indexed symbols
ckb_index_age_seconds Gauge Time since last index

CI Dashboard Integration

- name: Export Metrics to Datadog
  env:
    DD_API_KEY: ${{ secrets.DD_API_KEY }}
  run: |
    # Send CKB metrics to Datadog
    curl -s http://localhost:8080/metrics | \
      curl -X POST "https://api.datadoghq.com/api/v2/series" \
        -H "DD-API-KEY: $DD_API_KEY" \
        -H "Content-Type: application/json" \
        -d @-

Complexity Gates (v7.4)

Fail CI when code complexity exceeds thresholds. Prevents merging overly complex code.

Why This Matters

High cyclomatic complexity correlates with bugs and maintenance burden. Catching complexity early prevents technical debt accumulation.

Basic Complexity Check

- name: Complexity Gate
  run: |
    # Check complexity of changed files using CLI
    for FILE in $(git diff --name-only origin/${{ github.base_ref }}...HEAD | grep -E '\.(go|ts|py)$'); do
      if [ -f "$FILE" ]; then
        RESULT=$(ckb complexity "$FILE" --format=json 2>/dev/null || echo '{}')
        CYCLOMATIC=$(echo "$RESULT" | jq '.summary.maxCyclomatic // 0')
        COGNITIVE=$(echo "$RESULT" | jq '.summary.maxCognitive // 0')

        if [ "$CYCLOMATIC" -gt 15 ]; then
          echo "::error file=$FILE::Cyclomatic complexity $CYCLOMATIC exceeds threshold (15)"
          exit 1
        fi

        if [ "$COGNITIVE" -gt 20 ]; then
          echo "::warning file=$FILE::Cognitive complexity $COGNITIVE is high"
        fi
      fi
    done

Complexity Response

{
  "file": "internal/query/engine.go",
  "language": "go",
  "summary": {
    "functionCount": 31,
    "totalCyclomatic": 84,
    "totalCognitive": 75,
    "maxCyclomatic": 12,
    "maxCognitive": 18,
    "averageCyclomatic": 2.71,
    "averageCognitive": 2.42
  },
  "functions": [
    {"name": "Execute", "startLine": 45, "endLine": 120, "cyclomatic": 8, "cognitive": 12, "risk": "medium"},
    {"name": "parseQuery", "startLine": 122, "endLine": 150, "cyclomatic": 4, "cognitive": 6, "risk": "low"}
  ]
}

Team-Specific Thresholds

- name: Complexity Check
  run: |
    # Different thresholds for different areas
    check_complexity() {
      FILE=$1
      THRESHOLD=$2
      RESULT=$(curl -s "http://localhost:8080/complexity?path=$FILE")
      CYCLOMATIC=$(echo "$RESULT" | jq '.summary.maxCyclomatic // 0')

      if [ "$CYCLOMATIC" -gt "$THRESHOLD" ]; then
        echo "::error::$FILE has complexity $CYCLOMATIC (threshold: $THRESHOLD)"
        return 1
      fi
    }

    # Core code: strict threshold
    for FILE in $(git diff --name-only ${{ github.base_ref }}...HEAD | grep 'internal/core'); do
      check_complexity "$FILE" 10
    done

    # Generated code: relaxed threshold
    for FILE in $(git diff --name-only ${{ github.base_ref }}...HEAD | grep 'generated/'); do
      check_complexity "$FILE" 50
    done

Dead Code Detection (v6.4)

Identify unused code using telemetry data. Requires runtime telemetry to be enabled.

Why This Matters

Dead code increases maintenance burden, binary size, and security surface. Catching it in CI prevents accumulation.

Basic Dead Code Check

- name: Dead Code Check
  run: |
    RESULT=$(curl -s "http://localhost:8080/telemetry/dead-code?threshold=0.9&limit=20")
    CANDIDATES=$(echo "$RESULT" | jq '.candidates | length')

    if [ "$CANDIDATES" -gt 0 ]; then
      echo "## Dead Code Candidates" >> $GITHUB_STEP_SUMMARY
      echo "" >> $GITHUB_STEP_SUMMARY
      echo "$RESULT" | jq -r '.candidates[] | "- \(.symbol): \(.reason)"' >> $GITHUB_STEP_SUMMARY

      # Warning, not failure (telemetry may be incomplete)
      echo "::warning::Found $CANDIDATES potential dead code candidates"
    fi

Response Format

{
  "candidates": [
    {
      "symbol": "ckb:repo:sym:abc123",
      "name": "legacyHandler",
      "path": "internal/api/legacy.go:45",
      "reason": "No calls observed in 90 days",
      "confidence": 0.95,
      "lastSeen": "2024-09-15T00:00:00Z"
    }
  ],
  "telemetryPeriod": "90d",
  "coverage": 0.85
}

Blocking on New Dead Code

- name: Prevent New Dead Code
  run: |
    # Get dead code before and after
    BEFORE=$(curl -s "http://localhost:8080/telemetry/dead-code?ref=${{ github.base_ref }}" | jq '.candidates | length')
    AFTER=$(curl -s "http://localhost:8080/telemetry/dead-code" | jq '.candidates | length')

    if [ "$AFTER" -gt "$BEFORE" ]; then
      echo "::error::PR introduces $((AFTER - BEFORE)) new dead code candidates"
      exit 1
    fi

Coupling Analysis (v6.5)

Detect tightly coupled files that change together. Warns when coupled files change independently.

Why This Matters

When files are coupled (change together 80%+ of the time), changing one without the other is often a bug. CI can catch this.

Check for Broken Coupling

- name: Coupling Check
  run: |
    CHANGED=$(git diff --name-only origin/${{ github.base_ref }}...HEAD | tr '\n' ',' | sed 's/,$//')

    if [ -n "$CHANGED" ]; then
      # Use CLI command for coupling analysis
      ckb coupling --files="$CHANGED" --min-correlation=0.7 > coupling-result.json 2>/dev/null || echo '{}' > coupling-result.json

      MISSING=$(jq '.missingCoupled // [] | length' coupling-result.json)

      if [ "$MISSING" -gt 0 ]; then
        echo "## Coupling Warning" >> $GITHUB_STEP_SUMMARY
        echo "" >> $GITHUB_STEP_SUMMARY
        echo "These files usually change together but weren't included:" >> $GITHUB_STEP_SUMMARY
        jq -r '.missingCoupled[] | "- \(.file) (coupled with \(.coupledTo), \(.correlation * 100 | floor)%)"' coupling-result.json >> $GITHUB_STEP_SUMMARY

        echo "::warning::$MISSING tightly-coupled files may need updates"
      fi
    fi

Response Format

{
  "changedFiles": ["internal/api/handler.go"],
  "missingCoupled": [
    {
      "file": "internal/api/handler_test.go",
      "coupledTo": "internal/api/handler.go",
      "couplingScore": 0.92,
      "cochangeCount": 45
    }
  ],
  "recommendation": "Consider updating handler_test.go"
}

Cache Management

Control CKB's cache for optimal CI performance.

Pre-warming Cache

Warm the cache before analysis for faster queries:

- name: Warm Cache
  run: |
    ckb serve --port 8080 &
    sleep 2

    # Warm common queries
    curl -X POST "http://localhost:8080/cache/warm" \
      -H "Content-Type: application/json" \
      -d '{"scope": "architecture"}'

Clearing Cache

When you need fresh data:

- name: Clear Stale Cache
  run: |
    # Clear all cache
    curl -X POST "http://localhost:8080/cache/clear"

    # Or clear specific tier
    curl -X POST "http://localhost:8080/cache/clear" \
      -H "Content-Type: application/json" \
      -d '{"tier": "query"}'

Cache Tiers

Tier Contents TTL
query Symbol lookups, references 5 min
view Architecture, hotspots 1 hour
negative "Not found" results 10 min

Language Quality Dashboard (v7.3)

Validate multi-language project readiness across all languages in your codebase.

Check All Languages

- name: Language Quality Check
  run: |
    RESULT=$(curl -s "http://localhost:8080/meta/languages")

    # Find languages below quality threshold
    LOW_QUALITY=$(echo "$RESULT" | jq '[.languages[] | select(.quality < 0.7)] | length')

    if [ "$LOW_QUALITY" -gt 0 ]; then
      echo "## Language Quality Issues" >> $GITHUB_STEP_SUMMARY
      echo "" >> $GITHUB_STEP_SUMMARY
      echo "$RESULT" | jq -r '.languages[] | select(.quality < 0.7) | "- \(.name): \(.quality * 100 | floor)% (\(.issues | join(", ")))"' >> $GITHUB_STEP_SUMMARY

      echo "::warning::$LOW_QUALITY languages have quality issues"
    fi

Response Format

{
  "languages": [
    {
      "name": "go",
      "quality": 0.95,
      "indexer": "scip-go",
      "version": "0.3.0",
      "issues": []
    },
    {
      "name": "python",
      "quality": 0.60,
      "indexer": "scip-python",
      "version": null,
      "issues": ["Indexer not installed", "Virtual env not detected"]
    }
  ],
  "overallQuality": 0.78
}

Python-Specific Validation

- name: Python Environment Check
  if: hashFiles('**/*.py') != ''
  run: |
    RESULT=$(curl -s "http://localhost:8080/meta/python-env")

    if ! echo "$RESULT" | jq -e '.detected' > /dev/null; then
      echo "::warning::Python virtual environment not detected"
      echo "$RESULT" | jq .
    fi

TypeScript Monorepo Detection

- name: TypeScript Monorepo Check
  if: hashFiles('**/tsconfig.json') != ''
  run: |
    RESULT=$(curl -s "http://localhost:8080/meta/typescript-monorepo")

    PACKAGES=$(echo "$RESULT" | jq '.packages | length')
    echo "Detected $PACKAGES TypeScript packages"

    # Check for missing references
    MISSING=$(echo "$RESULT" | jq '.missingReferences | length')
    if [ "$MISSING" -gt 0 ]; then
      echo "::warning::$MISSING packages have missing tsconfig references"
    fi

Risk Audit (v6.5)

Comprehensive codebase risk analysis using 8 weighted factors. Identifies critical areas and suggests quick wins.

Risk Factors

Factor Weight Description
complexity 20% Cyclomatic/cognitive complexity
test_coverage 20% Percentage of code covered by tests
bus_factor 15% Number of contributors (single-author = risky)
security_sensitive 15% Contains auth/crypto/credential code
staleness 10% Time since last modification
error_rate 10% Runtime errors from telemetry
co_change_coupling 5% Tightly coupled files
churn 5% Frequency of changes

Basic Audit

- name: Risk Audit
  run: |
    RESULT=$(curl -s "http://localhost:8080/audit?minScore=60&limit=20")

    CRITICAL=$(echo "$RESULT" | jq '.summary.critical')
    HIGH=$(echo "$RESULT" | jq '.summary.high')

    echo "## Risk Audit Results" >> $GITHUB_STEP_SUMMARY
    echo "| Level | Count |" >> $GITHUB_STEP_SUMMARY
    echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY
    echo "| Critical | $CRITICAL |" >> $GITHUB_STEP_SUMMARY
    echo "| High | $HIGH |" >> $GITHUB_STEP_SUMMARY

    if [ "$CRITICAL" -gt 0 ]; then
      echo "::error::Found $CRITICAL critical risk files"
      exit 1
    fi

Response Format

{
  "repo": "myproject",
  "analyzedAt": "2024-12-23T10:00:00Z",
  "items": [
    {
      "file": "internal/auth/login.go",
      "riskScore": 82,
      "riskLevel": "critical",
      "factors": [
        {"factor": "security_sensitive", "value": "auth, token", "weight": 0.15, "contribution": 15},
        {"factor": "complexity", "value": "42", "weight": 0.20, "contribution": 20},
        {"factor": "bus_factor", "value": "1 author", "weight": 0.15, "contribution": 15}
      ],
      "recommendation": "Add tests and second reviewer for auth code"
    }
  ],
  "summary": {
    "critical": 3,
    "high": 12,
    "medium": 45,
    "low": 200,
    "topRiskFactors": [
      {"factor": "test_coverage", "count": 38},
      {"factor": "bus_factor", "count": 25}
    ]
  },
  "quickWins": [
    {"action": "Add tests", "target": "internal/auth/login.go", "effort": "medium", "impact": "high"}
  ]
}

Filter by Factor

- name: Security-Sensitive Audit
  run: |
    # Only show files with security-sensitive code
    curl -s "http://localhost:8080/audit?factor=security_sensitive&minScore=40" | jq '.items[] | "\(.file): \(.riskScore)"'

Quick Wins Report

- name: Quick Wins
  run: |
    RESULT=$(curl -s "http://localhost:8080/audit?quickWins=true")

    echo "## Quick Wins" >> $GITHUB_STEP_SUMMARY
    echo "$RESULT" | jq -r '.quickWins[] | "- **\(.action)**: \(.target) (effort: \(.effort), impact: \(.impact))"' >> $GITHUB_STEP_SUMMARY

Eval Suite (v7.0)

Built-in evaluation framework for validating CKB's code intelligence quality. Useful for CI regression testing.

Why This Matters

When upgrading CKB or changing indexes, you want to verify search quality hasn't regressed. The eval suite runs test cases against your codebase and reports pass/fail rates.

Basic Usage

- name: CKB Eval
  run: |
    # Run eval with fixture directory
    ckb eval --fixtures=.ckb/fixtures --format=json > eval-results.json

    PASSED=$(jq '.passedTests' eval-results.json)
    TOTAL=$(jq '.totalTests' eval-results.json)

    echo "## CKB Eval Results" >> $GITHUB_STEP_SUMMARY
    echo "Passed: $PASSED / $TOTAL" >> $GITHUB_STEP_SUMMARY

    # Fail if pass rate drops below threshold
    RATE=$(echo "scale=2; $PASSED / $TOTAL" | bc)
    if (( $(echo "$RATE < 0.90" | bc -l) )); then
      echo "::error::Eval pass rate $RATE below threshold (0.90)"
      exit 1
    fi

Fixture Format

Test fixtures are YAML files in .ckb/fixtures/:

# .ckb/fixtures/search-tests.yaml
- id: find-engine
  type: needle
  description: "Should find Engine struct"
  query: "Engine"
  expectedSymbols:
    - "internal/query/Engine"
  topK: 5

- id: ranking-handler
  type: ranking
  description: "Handler should rank above helper functions"
  query: "handle"
  expectedSymbols:
    - "handleRequest"
    - "handleResponse"
  scope: "internal/api"

Regression Detection

- name: Eval Regression Check
  run: |
    # Compare against baseline
    ckb eval --fixtures=.ckb/fixtures --format=json > current.json

    # Get baseline from artifact
    BASELINE_PASSED=$(cat baseline.json | jq '.passedTests')
    CURRENT_PASSED=$(cat current.json | jq '.passedTests')

    if [ "$CURRENT_PASSED" -lt "$BASELINE_PASSED" ]; then
      echo "::error::Eval regression: $CURRENT_PASSED passed vs baseline $BASELINE_PASSED"
      exit 1
    fi

Diff Summary (v6.5)

Summarize arbitrary git ranges, not just PRs. Useful for release notes and changelog generation.

Basic Usage

- name: Release Summary
  run: |
    # Summarize changes between tags
    RESULT=$(curl -s -X POST "http://localhost:8080/diff/summary" \
      -H "Content-Type: application/json" \
      -d '{"from": "v1.0.0", "to": "v1.1.0"}')

    echo "## Release Summary" >> $GITHUB_STEP_SUMMARY
    echo "" >> $GITHUB_STEP_SUMMARY
    echo "$RESULT" | jq -r '.summary' >> $GITHUB_STEP_SUMMARY

Response Format

{
  "from": "v1.0.0",
  "to": "v1.1.0",
  "summary": "15 files changed across 4 modules",
  "modules": [
    {"name": "internal/api", "additions": 200, "deletions": 50},
    {"name": "internal/query", "additions": 100, "deletions": 20}
  ],
  "breakingChanges": [
    {"symbol": "QueryOptions", "change": "Field 'Timeout' removed"}
  ],
  "newPublicSymbols": ["NewQueryEngine", "QueryResult"]
}

What This Is (and Isn't)

This is: A way to get structured JSON about PR changes that you can parse and post as comments. It combines git diff data with CKB's hotspot/ownership/contract analysis to surface risk before merge.

This is not:

  • Enforcement — it posts warnings, doesn't block merges by default
  • Magic — if you don't have CODEOWNERS, reviewers come from git-blame heuristics
  • Real-time — hotspot data comes from the last refreshArchitecture run

Recommended rollout:

  1. Start with just the PR comment (informational)
  2. Add hotspot warnings after you trust the data
  3. Add contract boundary detection for API changes (v6.3)
  4. Consider ownership drift on a schedule, not every PR
  5. Only add enforcement (failing builds) after the team trusts the signals

Environment Variables

Variable Description Default
CKB_REPO_ROOT Repository root path Current directory
CKB_LOG_LEVEL Log verbosity (debug/info/warn/error) info
CKB_CONFIG_PATH Config file location .ckb/config.json
CKB_TIER Analysis tier: fast, standard, or full Auto-detected

Tips

  1. Cache the .ckb/ directory — Enables incremental indexing across runs
  2. Use fetch-depth: 0 — Required for git-blame analysis and incremental indexing
  3. Run refresh on schedule — Keep data fresh without blocking PRs
  4. Set appropriate timeouts — Large repos may need longer timeouts
  5. Use async for big repos — Avoid CI timeout issues
  6. Use incremental indexing for PRs — O(changed files) instead of O(total files)
  7. Use --force for nightly builds — Ensures maximum accuracy
  8. Validate with ckb doctor --tier — Catch missing tools early
  9. Set CKB_TIER environment variable — Control analysis depth explicitly
  10. Use /ready for health checks — Wait for server before queries
  11. Export /metrics for dashboards — Track CKB performance over time
  12. Set complexity thresholds — Prevent complex code from merging
  13. Check coupling on PRs — Catch incomplete changes early
  14. Warm cache before analysis — Speed up repeated queries
  15. Run risk audit weekly — Track technical debt trends
  16. Use eval suite for upgrades — Catch search quality regressions

Example: Block High-Risk PRs

- name: Check Risk Level
  run: |
    RISK="${{ steps.summary.outputs.risk }}"
    if [ "$RISK" = "high" ]; then
      echo "::error::PR flagged as HIGH RISK. Manual review required."
      exit 1
    fi

Example: Auto-Request Reviewers

- name: Request Reviewers
  uses: actions/github-script@v7
  with:
    script: |
      const data = JSON.parse(`${{ steps.summary.outputs.result }}`);
      const reviewers = data.suggestedReviewers
        ?.filter(r => !r.owner.startsWith('@'))  // Filter out teams
        .map(r => r.owner)
        .slice(0, 2);

      if (reviewers?.length) {
        await github.rest.pulls.requestReviewers({
          owner: context.repo.owner,
          repo: context.repo.repo,
          pull_number: context.issue.number,
          reviewers: reviewers
        });
      }

Upload to Central Index Server (v7.3)

For organizations using a central CKB index server, publish indexes from CI:

# .github/workflows/publish-index.yml
name: Publish Index to CKB Server

on:
  push:
    branches: [main]

jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-go@v5
        with:
          go-version: '1.21'

      - name: Install SCIP indexer
        run: go install github.com/sourcegraph/scip-go/cmd/scip-go@latest

      - name: Generate SCIP index
        run: scip-go

      - name: Upload to CKB server
        env:
          CKB_TOKEN: ${{ secrets.CKB_TOKEN }}
        run: |
          curl -X POST "${{ vars.CKB_SERVER_URL }}/index/repos/${{ github.repository }}/upload" \
            -H "Authorization: Bearer $CKB_TOKEN" \
            -H "Content-Type: application/octet-stream" \
            -H "Content-Encoding: gzip" \
            -H "X-CKB-Commit: ${{ github.sha }}" \
            -H "X-CKB-Language: go" \
            --data-binary @<(gzip -c index.scip)

With compression (recommended for large indexes):

# gzip (widely available)
gzip -c index.scip | curl -X POST "$URL/upload" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Encoding: gzip" \
  --data-binary @-

# zstd (faster, better compression)
zstd -c index.scip | curl -X POST "$URL/upload" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Encoding: zstd" \
  --data-binary @-

Setting up tokens:

# On the server, create a write-scoped token for CI
ckb token create --name "GitHub CI" --scopes write --repos "myorg/*"
# Add the returned token as CKB_TOKEN secret in GitHub

See Authentication for token configuration.

Complete Example Workflows

Full example workflows are available in the repository:

  • examples/github-actions/pr-analysis.yml — PR analysis with comments
  • examples/github-actions/scheduled-refresh.yml — Daily architecture refresh
  • examples/github-actions/publish-index.yml — Publish to central index server

CI Template Library

Ready-to-use templates for common CI/CD platforms and local development hooks.

GitHub Actions Reusable Workflow

Create a reusable workflow that other repos can call:

.github/workflows/ckb-analysis.yml (in your template repo):

name: CKB Analysis

on:
  workflow_call:
    inputs:
      base-branch:
        description: 'Base branch for comparison'
        required: false
        default: 'main'
        type: string
      risk-threshold:
        description: 'Fail if risk exceeds (low/medium/high/critical)'
        required: false
        default: 'critical'
        type: string
      run-impact-analysis:
        description: 'Run change impact analysis'
        required: false
        default: true
        type: boolean
      post-comment:
        description: 'Post results as PR comment'
        required: false
        default: true
        type: boolean
    outputs:
      risk-level:
        description: 'Detected risk level'
        value: ${{ jobs.analyze.outputs.risk }}
      symbols-changed:
        description: 'Number of symbols changed'
        value: ${{ jobs.analyze.outputs.symbols }}

jobs:
  analyze:
    runs-on: ubuntu-latest
    outputs:
      risk: ${{ steps.impact.outputs.risk }}
      symbols: ${{ steps.impact.outputs.symbols }}

    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - uses: actions/setup-node@v4
        with:
          node-version: '20'

      - name: Install CKB
        run: npm install -g @tastehub/ckb

      - name: Restore Cache
        uses: actions/cache@v4
        with:
          path: .ckb/
          key: ckb-${{ runner.os }}-${{ github.ref }}-${{ github.sha }}
          restore-keys: |
            ckb-${{ runner.os }}-${{ github.ref }}-
            ckb-${{ runner.os }}-

      - name: Initialize and Index
        run: |
          ckb init
          ckb index

      - name: Run Impact Analysis
        id: impact
        if: inputs.run-impact-analysis
        run: |
          ckb impact diff --base=origin/${{ inputs.base-branch }} --format=json > impact.json

          RISK=$(jq -r '.summary.estimatedRisk // "unknown"' impact.json)
          SYMBOLS=$(jq '.summary.symbolsChanged // 0' impact.json)
          AFFECTED=$(jq '.summary.transitivelyAffected // 0' impact.json)

          echo "risk=$RISK" >> $GITHUB_OUTPUT
          echo "symbols=$SYMBOLS" >> $GITHUB_OUTPUT
          echo "affected=$AFFECTED" >> $GITHUB_OUTPUT

      - name: Run PR Summary
        id: summary
        run: |
          ckb pr-summary --base=origin/${{ inputs.base-branch }} --format=json > summary.json

      - name: Post PR Comment
        if: inputs.post-comment && github.event_name == 'pull_request'
        uses: actions/github-script@v7
        with:
          script: |
            const fs = require('fs');
            const impact = JSON.parse(fs.readFileSync('impact.json', 'utf8'));
            const summary = JSON.parse(fs.readFileSync('summary.json', 'utf8'));

            const emoji = {critical: '🔴', high: '🟠', medium: '🟡', low: '🟢'}[impact.summary?.estimatedRisk] || '⚪';

            let body = `## CKB Analysis ${emoji}\n\n`;
            body += `| Metric | Value |\n|--------|------:|\n`;
            body += `| Risk Level | **${impact.summary?.estimatedRisk || 'unknown'}** |\n`;
            body += `| Symbols Changed | ${impact.summary?.symbolsChanged || 0} |\n`;
            body += `| Directly Affected | ${impact.summary?.directlyAffected || 0} |\n`;
            body += `| Transitively Affected | ${impact.summary?.transitivelyAffected || 0} |\n`;
            body += `| Modules in Blast Radius | ${impact.blastRadius?.moduleCount || 0} |\n`;

            if (summary.suggestedReviewers?.length) {
              body += `\n### Suggested Reviewers\n`;
              summary.suggestedReviewers.forEach(r => {
                body += `- ${r.owner} (${Math.round(r.coverage * 100)}% coverage)\n`;
              });
            }

            if (impact.recommendations?.length) {
              body += `\n### Recommendations\n`;
              impact.recommendations.forEach(r => {
                const icon = r.severity === 'warning' ? '⚠️' : 'ℹ️';
                body += `- ${icon} ${r.message}\n`;
              });
            }

            github.rest.issues.createComment({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: context.issue.number,
              body
            });

      - name: Check Risk Threshold
        if: inputs.risk-threshold != ''
        run: |
          RISK="${{ steps.impact.outputs.risk }}"
          THRESHOLD="${{ inputs.risk-threshold }}"

          # Risk levels in order
          declare -A LEVELS=([low]=1 [medium]=2 [high]=3 [critical]=4)

          if [ "${LEVELS[$RISK]:-0}" -gt "${LEVELS[$THRESHOLD]:-4}" ]; then
            echo "::error::Risk level '$RISK' exceeds threshold '$THRESHOLD'"
            exit 1
          fi

      - name: Save Cache
        uses: actions/cache/save@v4
        if: always()
        with:
          path: .ckb/
          key: ckb-${{ runner.os }}-${{ github.ref }}-${{ github.sha }}

Usage in other repos:

# .github/workflows/pr.yml
name: PR Check

on:
  pull_request:

jobs:
  ckb:
    uses: your-org/workflows/.github/workflows/ckb-analysis.yml@main
    with:
      risk-threshold: 'high'
      post-comment: true

GitLab CI Template

.gitlab/ci/ckb.yml (include in your pipeline):

.ckb-base:
  image: node:20
  variables:
    CKB_LOG_LEVEL: info
  before_script:
    - npm install -g @tastehub/ckb
    - ckb init
  cache:
    key: ckb-${CI_COMMIT_REF_SLUG}
    paths:
      - .ckb/
    policy: pull-push

ckb-index:
  extends: .ckb-base
  stage: .pre
  script:
    - ckb index
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

ckb-impact-analysis:
  extends: .ckb-base
  stage: test
  needs: [ckb-index]
  script:
    - |
      ckb impact diff --base=origin/${CI_MERGE_REQUEST_TARGET_BRANCH_NAME:-$CI_DEFAULT_BRANCH} --format=json > impact.json

      RISK=$(jq -r '.summary.estimatedRisk' impact.json)
      SYMBOLS=$(jq '.summary.symbolsChanged' impact.json)
      AFFECTED=$(jq '.summary.transitivelyAffected' impact.json)

      echo "Risk Level: $RISK"
      echo "Symbols Changed: $SYMBOLS"
      echo "Transitively Affected: $AFFECTED"

      # Fail on critical risk
      if [ "$RISK" = "critical" ]; then
        echo "CRITICAL RISK: Manual review required"
        exit 1
      fi
  artifacts:
    reports:
      dotenv: impact.env
    paths:
      - impact.json
    expire_in: 1 week
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"

ckb-pr-summary:
  extends: .ckb-base
  stage: test
  needs: [ckb-index]
  script:
    - |
      ckb pr-summary --base=origin/${CI_MERGE_REQUEST_TARGET_BRANCH_NAME:-$CI_DEFAULT_BRANCH} --format=json > summary.json

      # Generate MR note
      echo "## CKB Analysis" > mr-note.md
      echo "" >> mr-note.md
      jq -r '"Risk: **" + .riskAssessment.level + "**"' summary.json >> mr-note.md
      echo "" >> mr-note.md
      jq -r '"Files: " + (.summary.totalFiles | tostring)' summary.json >> mr-note.md

      # Post to MR (requires GITLAB_TOKEN with api scope)
      if [ -n "$GITLAB_TOKEN" ]; then
        curl --request POST \
          --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
          --form "body=$(cat mr-note.md)" \
          "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/merge_requests/${CI_MERGE_REQUEST_IID}/notes"
      fi
  artifacts:
    paths:
      - summary.json
      - mr-note.md
    expire_in: 1 week
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"

ckb-scheduled-refresh:
  extends: .ckb-base
  stage: build
  script:
    - ckb index --force
    - ckb arch > architecture.json
    - ckb hotspots --limit=50 > hotspots.json
  artifacts:
    paths:
      - architecture.json
      - hotspots.json
    expire_in: 30 days
  rules:
    - if: $CI_PIPELINE_SOURCE == "schedule"

Include in your .gitlab-ci.yml:

include:
  - local: '.gitlab/ci/ckb.yml'

stages:
  - .pre
  - test
  - build

Pre-commit Hook

.pre-commit-config.yaml:

repos:
  - repo: local
    hooks:
      - id: ckb-impact-check
        name: CKB Impact Analysis
        entry: bash -c 'ckb impact diff --staged --format=json | jq -e ".summary.estimatedRisk != \"critical\"" || (echo "CRITICAL RISK: Review changes before committing" && exit 1)'
        language: system
        pass_filenames: false
        stages: [commit]

      - id: ckb-complexity-check
        name: CKB Complexity Check
        entry: bash -c 'for f in "$@"; do result=$(ckb complexity "$f" --format=json 2>/dev/null); max=$(echo "$result" | jq ".summary.maxCyclomatic // 0"); if [ "$max" -gt 15 ]; then echo "$f: cyclomatic complexity $max exceeds 15"; exit 1; fi; done'
        language: system
        types: [file]
        files: \.(go|ts|js|py)$
        stages: [commit]

Manual git hook (.git/hooks/pre-commit):

#!/bin/bash
set -e

# Check if CKB is installed
if ! command -v ckb &> /dev/null; then
    echo "CKB not installed, skipping impact analysis"
    exit 0
fi

# Run impact analysis on staged changes
echo "Running CKB impact analysis..."
RESULT=$(ckb impact diff --staged --format=json 2>/dev/null || echo '{"summary":{}}')

RISK=$(echo "$RESULT" | jq -r '.summary.estimatedRisk // "unknown"')
SYMBOLS=$(echo "$RESULT" | jq '.summary.symbolsChanged // 0')

if [ "$RISK" = "critical" ]; then
    echo ""
    echo "⛔ CRITICAL RISK DETECTED"
    echo "   Symbols changed: $SYMBOLS"
    echo "   Transitively affected: $(echo "$RESULT" | jq '.summary.transitivelyAffected // 0')"
    echo ""
    echo "   Review the impact before committing:"
    echo "   $ ckb impact diff --staged"
    echo ""
    echo "   To bypass: git commit --no-verify"
    exit 1
elif [ "$RISK" = "high" ]; then
    echo ""
    echo "⚠️  HIGH RISK CHANGE"
    echo "   Symbols changed: $SYMBOLS"
    echo "   Consider running full test suite before pushing."
    echo ""
fi

exit 0

Make executable: chmod +x .git/hooks/pre-commit


Husky + lint-staged (JS/TS)

Install:

npm install -D husky lint-staged
npx husky init

package.json:

{
  "scripts": {
    "prepare": "husky",
    "ckb:impact": "ckb impact diff --staged --format=json",
    "ckb:check": "node scripts/ckb-check.js"
  },
  "lint-staged": {
    "*.{ts,tsx,js,jsx}": [
      "eslint --fix",
      "prettier --write"
    ],
    "*.{ts,tsx}": [
      "npm run ckb:check"
    ]
  }
}

.husky/pre-commit:

#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

# Run lint-staged for linting/formatting
npx lint-staged

# Run CKB impact analysis
if command -v ckb &> /dev/null; then
  RESULT=$(ckb impact diff --staged --format=json 2>/dev/null || echo '{}')
  RISK=$(echo "$RESULT" | node -e "const d=require('fs').readFileSync('/dev/stdin','utf8');const j=JSON.parse(d||'{}');console.log(j.summary?.estimatedRisk||'unknown')")

  if [ "$RISK" = "critical" ]; then
    echo ""
    echo "⛔ CKB detected CRITICAL risk in staged changes"
    echo "   Run 'ckb impact diff --staged' for details"
    echo "   Bypass with: git commit --no-verify"
    exit 1
  fi
fi

scripts/ckb-check.js:

#!/usr/bin/env node
const { execSync } = require('child_process');
const path = require('path');

// Get staged files from arguments
const files = process.argv.slice(2);

if (files.length === 0) {
  process.exit(0);
}

// Check complexity for each file
let hasError = false;

for (const file of files) {
  try {
    const result = execSync(`ckb complexity "${file}" --format=json`, {
      encoding: 'utf8',
      stdio: ['pipe', 'pipe', 'pipe']
    });

    const data = JSON.parse(result);
    const maxCyclomatic = data.summary?.maxCyclomatic || 0;
    const maxCognitive = data.summary?.maxCognitive || 0;

    if (maxCyclomatic > 15) {
      console.error(`❌ ${file}: cyclomatic complexity ${maxCyclomatic} exceeds 15`);
      hasError = true;
    } else if (maxCognitive > 20) {
      console.warn(`⚠️  ${file}: cognitive complexity ${maxCognitive} is high`);
    }
  } catch (e) {
    // CKB not available or file not analyzable, skip
  }
}

if (hasError) {
  console.error('\nRefactor complex functions before committing.');
  console.error('Bypass with: git commit --no-verify');
  process.exit(1);
}

Make check script executable:

chmod +x scripts/ckb-check.js

Template Summary

Template File Features
PR Comment Widget github-actions/impact-comment.yml Simple sticky PR comments
Full Workflow github-actions/impact-analysis.yml Risk gates, reviewers, comments
Reusable Workflow github-actions/ckb-reusable.yml Org-wide standardization
Scheduled Refresh github-actions/ckb-refresh.yml Daily architecture updates
GitLab CI gitlab-ci/ckb.yml MR notes, affected tests
Pre-commit Hook hooks/pre-commit Block critical commits
pre-commit Framework hooks/pre-commit-config.yaml Complexity checks
Husky hooks/husky/ lint-staged integration

Quick install:

# GitHub Actions - Simple PR comment
curl -o .github/workflows/impact-comment.yml \
  https://raw.githubusercontent.com/SimplyLiz/CodeMCP/main/examples/github-actions/impact-comment.yml

# GitHub Actions - Full workflow
curl -o .github/workflows/impact-analysis.yml \
  https://raw.githubusercontent.com/SimplyLiz/CodeMCP/main/examples/github-actions/impact-analysis.yml

# GitLab CI
mkdir -p .gitlab/ci
curl -o .gitlab/ci/ckb.yml \
  https://raw.githubusercontent.com/SimplyLiz/CodeMCP/main/examples/gitlab-ci/ckb.yml

# Pre-commit hook
curl -o .git/hooks/pre-commit \
  https://raw.githubusercontent.com/SimplyLiz/CodeMCP/main/examples/hooks/pre-commit
chmod +x .git/hooks/pre-commit

# Husky (after npm install -D husky lint-staged)
npx husky init
curl -o .husky/pre-commit \
  https://raw.githubusercontent.com/SimplyLiz/CodeMCP/main/examples/hooks/husky/pre-commit
chmod +x .husky/pre-commit

See examples/README for full documentation.


Related Pages

Clone this wiki locally