-
-
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 # Required# .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
});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
});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:
- 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 |
-
Cache the
.ckb/directory — Speeds up subsequent runs -
Use
fetch-depth: 0— Required for git-blame analysis - 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
- 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
});
}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