-
-
Notifications
You must be signed in to change notification settings - Fork 11
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.)
When you call ckb pr-summary, CKB:
- Diffs the branches using git (base vs head)
- Maps files to modules based on path structure
- Checks each file against hotspot data — flags files with score > 0.5
- Queries ownership for each changed file (CODEOWNERS + git-blame)
- Calculates risk based on: file count, lines changed, hotspots touched, module spread
- 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"}
]
}CKB runs as CLI commands inside the runner. The workflow:
- Checkout with full history (
fetch-depth: 0) - Install CKB via npm or binary download
- Initialize:
ckb init - Run analysis:
ckb pr-summary --base=origin/main - Parse JSON output, post PR comment
npm (Recommended):
- uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install CKB
run: npm install -g @tastehub/ckbOr use npx (no install):
- uses: actions/setup-node@v4
with:
node-version: '20'
- name: Initialize CKB
run: npx @tastehub/ckb initOr 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/ckbCKB needs git history for ownership analysis. Without it, suggested reviewers will be empty.
- uses: actions/checkout@v4
with:
fetch-depth: 0 # RequiredBefore 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.
- 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)"'
fiDifferent 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
}- 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# .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
});For Go projects, CKB supports incremental indexing that only processes changed files. This can dramatically speed up CI pipelines.
| 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).
- 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"# 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 indexUse --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.
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.shafor exact match on re-runs - Include
github.refto separate branch caches - Use
restore-keysto fall back to older caches
# .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 }}CKB uses a lock file (.ckb/index.lock) to prevent concurrent indexing. In CI:
- If a previous step is still indexing, subsequent
ckb indexcalls 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.
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 pushFor 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
doneCKB provides two interfaces for CI/CD integration:
- CLI commands — Direct invocation, simpler setup, recommended for most use cases
- HTTP API — Start server first, useful for multiple queries or daemon mode
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.3Start 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"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 |
When changes touch API contracts (protobuf, OpenAPI), CKB can detect cross-boundary impact that simple file diffs miss.
A "local" change to user.proto might break consumers in three other repositories. Without contract analysis, this surfaces as production incidents, not PR warnings.
- 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": "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"
}- 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
doneWhen 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
});CKB can enforce documentation coverage thresholds and detect stale symbol references in CI.
Documentation that references deleted or renamed symbols becomes misleading. A function signature change can leave docs describing behavior that no longer exists.
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=80Exit codes:
-
0: Coverage meets or exceeds threshold -
1: Coverage below threshold
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
fiFind 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# .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
fiFor Kubernetes deployments or orchestration systems, CKB provides standard health endpoints.
- 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| 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 |
# In your k8s deployment
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 2
periodSeconds: 5CKB exports metrics in Prometheus format for monitoring and dashboards.
- 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"| 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 |
- 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 @-Fail CI when code complexity exceeds thresholds. Prevents merging overly complex code.
High cyclomatic complexity correlates with bugs and maintenance burden. Catching complexity early prevents technical debt accumulation.
- 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{
"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"}
]
}- 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
doneIdentify unused code using telemetry data. Requires runtime telemetry to be enabled.
Dead code increases maintenance burden, binary size, and security surface. Catching it in CI prevents accumulation.
- 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{
"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
}- 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
fiDetect tightly coupled files that change together. Warns when coupled files change independently.
When files are coupled (change together 80%+ of the time), changing one without the other is often a bug. CI can catch this.
- 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{
"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"
}Control CKB's cache for optimal CI performance.
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"}'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"}'| Tier | Contents | TTL |
|---|---|---|
query |
Symbol lookups, references | 5 min |
view |
Architecture, hotspots | 1 hour |
negative |
"Not found" results | 10 min |
Validate multi-language project readiness across all languages in your codebase.
- 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{
"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
}- 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- 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"
fiComprehensive codebase risk analysis using 8 weighted factors. Identifies critical areas and suggests quick wins.
| 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 |
- 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{
"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"}
]
}- 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)"'- 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_SUMMARYBuilt-in evaluation framework for validating CKB's code intelligence quality. Useful for CI regression testing.
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.
- 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
fiTest 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"- 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
fiSummarize arbitrary git ranges, not just PRs. Useful for release notes and changelog generation.
- 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{
"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"]
}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
refreshArchitecturerun
Recommended rollout:
- Start with just the PR comment (informational)
- Add hotspot warnings after you trust the data
- Add contract boundary detection for API changes (v6.3)
- Consider ownership drift on a schedule, not every PR
- Only add enforcement (failing builds) after the team trusts the signals
| 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 |
-
Cache the
.ckb/directory — Enables incremental indexing across runs -
Use
fetch-depth: 0— Required for git-blame analysis and incremental indexing - Run refresh on schedule — Keep data fresh without blocking PRs
- Set appropriate timeouts — Large repos may need longer timeouts
- Use async for big repos — Avoid CI timeout issues
- Use incremental indexing for PRs — O(changed files) instead of O(total files)
-
Use
--forcefor nightly builds — Ensures maximum accuracy -
Validate with
ckb doctor --tier— Catch missing tools early -
Set
CKB_TIERenvironment variable — Control analysis depth explicitly -
Use
/readyfor health checks — Wait for server before queries -
Export
/metricsfor dashboards — Track CKB performance over time - Set complexity thresholds — Prevent complex code from merging
- Check coupling on PRs — Catch incomplete changes early
- Warm cache before analysis — Speed up repeated queries
- Run risk audit weekly — Track technical debt trends
- Use eval suite for upgrades — Catch search quality regressions
- 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- 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
});
}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 GitHubSee Authentication for token configuration.
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
- Authentication — Token management and scopes
- Daemon-Mode — Webhook-triggered indexing
- Federation — Cross-repo index publishing
- Incremental-Indexing — Efficient index updates
- Configuration — CI-related configuration options