-
-
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
The pattern: start CKB as a local server, call it with curl, parse JSON responses.
When you call POST /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 a local HTTP server inside the runner. The workflow:
- Checkout with full history (
fetch-depth: 0) - Install CKB
- Start server:
ckb serve --port 8080 & - Call endpoints with
curl - Parse JSON, 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
ckb doctor --fix
- name: Start CKB Server
run: |
ckb serve --port 8080 &
sleep 2 # Wait for server to start
- name: Run PR Summary
id: summary
run: |
RESULT=$(curl -s -X POST http://localhost:8080/pr/summary \
-H "Content-Type: application/json" \
-d '{"baseBranch": "${{ github.base_ref }}"}')
RISK=$(echo "$RESULT" | jq -r '.riskAssessment.level // "unknown"')
echo "risk=$RISK" >> $GITHUB_OUTPUT
echo "result<<EOF" >> $GITHUB_OUTPUT
echo "$RESULT" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- name: Post Comment
uses: actions/github-script@v7
with:
script: |
const data = JSON.parse(`${{ steps.summary.outputs.result }}`);
let emoji = data.riskAssessment?.level === 'high' ? '🔴' :
data.riskAssessment?.level === 'medium' ? '🟡' : '🟢';
let body = `## CKB Analysis ${emoji}\n\n`;
body += `**Risk:** ${data.riskAssessment?.level || 'unknown'}\n`;
body += `**Files:** ${data.summary?.totalFiles || 0}\n`;
body += `**Hotspots touched:** ${data.summary?.hotspotsTouched || 0}\n\n`;
if (data.suggestedReviewers?.length) {
body += `### Suggested Reviewers\n`;
data.suggestedReviewers.slice(0,3).forEach(r => {
body += `- ${r.owner}\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
donePOST /pr/summary — The main CI endpoint
Request:
{"baseBranch": "main", "headBranch": "HEAD"}Returns: File counts, module impacts, hotspots touched, risk assessment (low/medium/high), suggested reviewers with coverage percentages.
Risk is calculated from:
- File count (>20 files = +0.3 risk)
- Lines changed (>1000 = +0.3 risk)
- Hotspots touched (+0.1 per hotspot, max 0.3)
- Module spread (>5 modules = +0.2 risk)
GET /ownership/drift — CODEOWNERS accuracy check
curl "http://localhost:8080/ownership/drift?threshold=0.3&limit=10"Compares declared owners (CODEOWNERS) vs actual contributors (git-blame). Returns files where ownership has drifted, with drift scores.
GET /hotspots — High-churn files
curl "http://localhost:8080/hotspots?limit=20"Returns files ranked by volatility (commit frequency, author count, complexity).
POST /architecture/refresh — Rebuild cached analysis
curl -X POST http://localhost:8080/architecture/refresh \
-H "Content-Type: application/json" \
-d '{"scope": "all", "async": true}'With async: true, returns a jobId immediately. Poll /jobs/:id for completion.
GET /jobs/:id — Check async job status
curl "http://localhost:8080/jobs/job-abc123"Returns: status (queued/running/completed/failed), progress (0-100), result (when done).
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
fiThis 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:
- A CLI command you can run directly (
ckb summarize-prdoesn't exist yet) - 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
The awkward part: You have to start a server (ckb serve &) and call it with curl. This is because v6.1 implemented the logic as HTTP handlers, not CLI commands. It works, but it's not as clean as ckb summarize-pr --base=main.
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
- 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