Integration Guide
How to use CKB in your own tools, scripts, and applications.
What Can You Build?
CKB turns your codebase into a queryable knowledge base. Here are ideas to spark your imagination:
Developer Tools
| Idea | Description | CKB Features Used |
|---|---|---|
| Smart Code Search | IDE extension that finds symbols semantically, not just text | searchSymbols, findReferences |
| Refactoring Assistant | Show blast radius before renaming/moving code | analyzeImpact, getCallGraph |
| Dead Code Finder | Dashboard showing unused code with confidence scores | findDeadCodeCandidates, justifySymbol |
| Dependency Visualizer | Interactive graph of module dependencies | getArchitecture, getCallGraph |
| Code Tour Generator | Auto-generate onboarding tours for new devs | listEntrypoints, traceUsage |
CI/CD Automation
| Idea | Description | CKB Features Used |
|---|---|---|
| Smart Test Selection | Run only tests affected by changes (save CI time) | getAffectedTests |
| PR Risk Scorer | Auto-label PRs as low/medium/high risk | analyzeChange, getHotspots |
| Auto-Reviewer Assignment | Assign reviewers based on code ownership | getChangeOwners, getOwnership |
| Breaking Change Detector | Fail PRs that break API contracts | analyzeImpact, listContracts |
| Doc Freshness Check | Fail if docs reference renamed/deleted code | checkDocStaleness |
Dashboards & Reports
| Idea | Description | CKB Features Used |
|---|---|---|
| Tech Debt Tracker | Weekly report of hotspots, dead code, complexity | getHotspots, auditRisk |
| Architecture Health | Module coupling trends over time | analyzeCoupling, getArchitecture |
| Ownership Map | Who owns what? Visualize CODEOWNERS coverage | getOwnership, getOwnershipDrift |
| API Surface Monitor | Track public API changes across versions | listEntrypoints, analyzeImpact |
| Onboarding Progress | Track which parts of codebase new devs have explored | getModuleOverview, listKeyConcepts |
AI/LLM Applications
| Idea | Description | CKB Features Used |
|---|---|---|
| Codebase Q&A Bot | Slack bot that answers "who owns X?" "what calls Y?" | Any MCP tool |
| PR Summary Generator | Auto-generate PR descriptions with impact analysis | summarizePr, analyzeChange |
| Code Review Assistant | AI that flags risky changes and suggests reviewers | analyzeChange, getChangeOwners |
| Architecture Explainer | "Explain how authentication works in this codebase" | traceUsage, explainSymbol |
| Refactoring Planner | "Help me safely rename UserService" with step-by-step plan | analyzeImpact, findReferences |
Team Workflows
| Idea | Description | CKB Features Used |
|---|---|---|
| Knowledge Transfer Tool | When someone leaves, identify their owned code | getOwnership, bus factor from auditRisk |
| Sprint Planning Helper | Estimate risk of planned changes | analyzeImpact on planned files |
| Incident Response | "What else might be affected by this bug?" | getCallGraph, analyzeImpact |
| Migration Tracker | Track progress of framework/library migrations | searchSymbols, findReferences |
Quick Wins to Try Today
Start small with these one-liner integrations:
# Add to your shell aliases
alias risk='ckb impact diff --format=json | jq ".data.summary.estimatedRisk"'
alias tests='$(ckb affected-tests --output=command)'
alias owners='ckb reviewers --format=gh'
# Pre-push hook: warn on high-risk changes
echo 'ckb impact diff --staged | grep -q "high\|critical" && echo "⚠️ High risk changes"' >> .git/hooks/pre-push
# Daily slack reminder of hotspots
# (add to cron)
0 9 * * 1 ckb hotspots --format=json | jq -r '.data.hotspots[:3] | .[] | "🔥 \(.file)"' | slack-post
CKB provides three integration methods:
| Method | Best For | Latency | Setup |
|---|---|---|---|
| CLI | Scripts, CI/CD, one-off queries | ~100ms startup | None |
| HTTP API | Web apps, services, custom tools | ~5ms per request | Start server |
| MCP | AI assistants, LLM tools | ~5ms per request | Configure client |
CLI Integration
The simplest way to integrate CKB into scripts and automation.
Basic Usage
# All commands output JSON with --format=json
ckb search "Handler" --format=json
ckb impact "symbol-id" --format=json
ckb hotspots --format=json
Shell Scripts
#!/bin/bash
# Example: Get high-risk files and run tests on them
# Get hotspots as JSON, extract file paths
HIGH_RISK=$(ckb hotspots --format=json | jq -r '.data.hotspots[:5] | .[].file')
# Run tests for those files
for file in $HIGH_RISK; do
echo "Testing $file..."
go test "./${file%/*}/..."
done
CI/CD Integration
# GitHub Actions example
- name: Analyze PR Impact
run: |
ckb index --if-stale=24h
IMPACT=$(ckb impact diff --base=origin/main --format=json)
RISK=$(echo "$IMPACT" | jq -r '.data.summary.estimatedRisk')
if [ "$RISK" = "critical" ]; then
echo "::error::Critical risk detected in PR"
exit 1
fi
Node.js Scripts
const { execSync } = require('child_process');
function ckb(command) {
const result = execSync(`ckb ${command} --format=json`, {
encoding: 'utf8',
maxBuffer: 10 * 1024 * 1024 // 10MB for large results
});
return JSON.parse(result);
}
// Find all callers of a function
const impact = ckb('impact "internal/api.HandleRequest"');
console.log(`Blast radius: ${impact.data.blastRadius.riskLevel}`);
console.log(`Affected modules: ${impact.data.blastRadius.moduleCount}`);
// Get affected tests
const tests = ckb('affected-tests --format=list');
console.log('Tests to run:', tests);
Python Scripts
import subprocess
import json
def ckb(command: str) -> dict:
"""Run a CKB command and return parsed JSON."""
result = subprocess.run(
f"ckb {command} --format=json",
shell=True,
capture_output=True,
text=True
)
if result.returncode != 0:
raise Exception(f"CKB error: {result.stderr}")
return json.loads(result.stdout)
# Example: Find dead code candidates
dead_code = ckb("dead-code --min-confidence=0.8")
for symbol in dead_code["data"]["candidates"]:
print(f"Unused: {symbol['name']} in {symbol['file']}")
# Example: Check documentation coverage
coverage = ckb("docs coverage")
print(f"Doc coverage: {coverage['data']['percentage']}%")
Useful CLI Patterns
# Pipe to jq for filtering
ckb search "Service" --format=json | jq '.data.symbols[] | select(.kind == "class")'
# Get just file paths
ckb hotspots --format=json | jq -r '.data.hotspots[].file'
# Check if index is fresh (exit code 0 = fresh, 1 = stale)
ckb status --format=json | jq -e '.data.index.isFresh' > /dev/null
# Run only affected tests
$(ckb affected-tests --output=command)
# Get reviewers for gh CLI
gh pr edit --add-reviewer "$(ckb reviewers --format=gh)"
HTTP API
For applications that need low-latency, persistent connections.
Starting the Server
# Default: localhost:8080
ckb serve
# Custom port and host
ckb serve --port 9000 --host 0.0.0.0
# With authentication
ckb serve --require-auth
Authentication
If authentication is enabled, include the token in requests:
curl -H "Authorization: Bearer $CKB_TOKEN" http://localhost:8080/api/v1/symbols
API Endpoints
Search Symbols
curl "http://localhost:8080/api/v1/symbols?q=Handler&limit=10"
Response:
{
"symbols": [
{
"id": "scip-go gomod example.com/app . internal/api.Handler",
"name": "Handler",
"kind": "function",
"file": "internal/api/handler.go",
"line": 42
}
],
"total": 15,
"truncated": true
}
Get Symbol Details
curl "http://localhost:8080/api/v1/symbols/scip-go%20gomod%20example.com%2Fapp%20.%20internal%2Fapi.Handler"
Find References
curl "http://localhost:8080/api/v1/symbols/SYMBOL_ID/references"
Impact Analysis
curl "http://localhost:8080/api/v1/impact?symbol=SYMBOL_ID&depth=2"
Analyze Diff
curl -X POST "http://localhost:8080/api/v1/impact/diff" \
-H "Content-Type: application/json" \
-d '{"base": "main", "head": "HEAD"}'
Get Hotspots
curl "http://localhost:8080/api/v1/hotspots?limit=10"
Trigger Refresh
# Incremental refresh
curl -X POST "http://localhost:8080/api/v1/refresh"
# Full reindex
curl -X POST "http://localhost:8080/api/v1/refresh" \
-d '{"full": true}'
JavaScript/TypeScript Client
class CKBClient {
constructor(
private baseUrl: string = 'http://localhost:8080',
private token?: string
) {}
private async fetch<T>(path: string, options?: RequestInit): Promise<T> {
const headers: Record<string, string> = {
'Content-Type': 'application/json',
};
if (this.token) {
headers['Authorization'] = `Bearer ${this.token}`;
}
const response = await fetch(`${this.baseUrl}${path}`, {
...options,
headers: { ...headers, ...options?.headers },
});
if (!response.ok) {
throw new Error(`CKB API error: ${response.status}`);
}
return response.json();
}
async searchSymbols(query: string, limit = 20) {
return this.fetch(`/api/v1/symbols?q=${encodeURIComponent(query)}&limit=${limit}`);
}
async getImpact(symbolId: string, depth = 2) {
return this.fetch(`/api/v1/impact?symbol=${encodeURIComponent(symbolId)}&depth=${depth}`);
}
async getHotspots(limit = 10) {
return this.fetch(`/api/v1/hotspots?limit=${limit}`);
}
async analyzeDiff(base: string, head: string) {
return this.fetch('/api/v1/impact/diff', {
method: 'POST',
body: JSON.stringify({ base, head }),
});
}
async refresh(full = false) {
return this.fetch('/api/v1/refresh', {
method: 'POST',
body: JSON.stringify({ full }),
});
}
}
// Usage
const ckb = new CKBClient('http://localhost:8080', process.env.CKB_TOKEN);
const symbols = await ckb.searchSymbols('UserService');
const impact = await ckb.getImpact(symbols.symbols[0].id);
console.log(`Risk level: ${impact.blastRadius.riskLevel}`);
Python Client
import requests
from typing import Optional
from dataclasses import dataclass
@dataclass
class CKBClient:
base_url: str = "http://localhost:8080"
token: Optional[str] = None
def _headers(self) -> dict:
headers = {"Content-Type": "application/json"}
if self.token:
headers["Authorization"] = f"Bearer {self.token}"
return headers
def search_symbols(self, query: str, limit: int = 20) -> dict:
resp = requests.get(
f"{self.base_url}/api/v1/symbols",
params={"q": query, "limit": limit},
headers=self._headers()
)
resp.raise_for_status()
return resp.json()
def get_impact(self, symbol_id: str, depth: int = 2) -> dict:
resp = requests.get(
f"{self.base_url}/api/v1/impact",
params={"symbol": symbol_id, "depth": depth},
headers=self._headers()
)
resp.raise_for_status()
return resp.json()
def get_hotspots(self, limit: int = 10) -> dict:
resp = requests.get(
f"{self.base_url}/api/v1/hotspots",
params={"limit": limit},
headers=self._headers()
)
resp.raise_for_status()
return resp.json()
def analyze_diff(self, base: str, head: str) -> dict:
resp = requests.post(
f"{self.base_url}/api/v1/impact/diff",
json={"base": base, "head": head},
headers=self._headers()
)
resp.raise_for_status()
return resp.json()
def refresh(self, full: bool = False) -> dict:
resp = requests.post(
f"{self.base_url}/api/v1/refresh",
json={"full": full},
headers=self._headers()
)
resp.raise_for_status()
return resp.json()
# Usage
import os
ckb = CKBClient(token=os.environ.get("CKB_TOKEN"))
# Find a symbol and analyze impact
symbols = ckb.search_symbols("HandleRequest")
if symbols["symbols"]:
impact = ckb.get_impact(symbols["symbols"][0]["id"])
print(f"Blast radius: {impact['blastRadius']['riskLevel']}")
print(f"Affected modules: {impact['blastRadius']['moduleCount']}")
Go Client
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"net/url"
)
type CKBClient struct {
BaseURL string
Token string
client *http.Client
}
func NewCKBClient(baseURL, token string) *CKBClient {
return &CKBClient{
BaseURL: baseURL,
Token: token,
client: &http.Client{},
}
}
func (c *CKBClient) do(method, path string, body interface{}) (map[string]interface{}, error) {
var reqBody *bytes.Buffer
if body != nil {
data, _ := json.Marshal(body)
reqBody = bytes.NewBuffer(data)
}
req, err := http.NewRequest(method, c.BaseURL+path, reqBody)
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
if c.Token != "" {
req.Header.Set("Authorization", "Bearer "+c.Token)
}
resp, err := c.client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
return result, nil
}
func (c *CKBClient) SearchSymbols(query string, limit int) (map[string]interface{}, error) {
path := fmt.Sprintf("/api/v1/symbols?q=%s&limit=%d", url.QueryEscape(query), limit)
return c.do("GET", path, nil)
}
func (c *CKBClient) GetImpact(symbolID string, depth int) (map[string]interface{}, error) {
path := fmt.Sprintf("/api/v1/impact?symbol=%s&depth=%d", url.QueryEscape(symbolID), depth)
return c.do("GET", path, nil)
}
func (c *CKBClient) AnalyzeDiff(base, head string) (map[string]interface{}, error) {
return c.do("POST", "/api/v1/impact/diff", map[string]string{
"base": base,
"head": head,
})
}
// Usage
func main() {
ckb := NewCKBClient("http://localhost:8080", os.Getenv("CKB_TOKEN"))
symbols, _ := ckb.SearchSymbols("Handler", 10)
fmt.Printf("Found %v symbols\n", symbols["total"])
}
MCP Integration
For AI assistants and LLM-powered tools.
What is MCP?
The Model Context Protocol (MCP) is a standard for connecting AI assistants to external tools. CKB implements an MCP server that exposes 76 tools for code intelligence.
Starting the MCP Server
# Default preset (14 core tools)
ckb mcp
# Specific preset
ckb mcp --preset=review # 19 tools for code review
ckb mcp --preset=refactor # 19 tools for refactoring
ckb mcp --preset=full # All 76 tools
# With auto-reindexing
ckb mcp --watch
Configuring AI Tools
Claude Code
# Automatic setup
ckb setup --tool=claude-code
# Or manual: add to .mcp.json
{
"mcpServers": {
"ckb": {
"command": "ckb",
"args": ["mcp", "--preset=core"]
}
}
}
Cursor
ckb setup --tool=cursor
# Or add to .cursor/mcp.json
{
"mcpServers": {
"ckb": {
"command": "npx",
"args": ["@anthropic/ckb", "mcp"]
}
}
}
Custom MCP Client
If you're building your own MCP client:
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
// Start CKB as subprocess
const transport = new StdioClientTransport({
command: "ckb",
args: ["mcp", "--preset=core"]
});
const client = new Client({
name: "my-tool",
version: "1.0.0"
});
await client.connect(transport);
// List available tools
const tools = await client.listTools();
console.log(`CKB provides ${tools.tools.length} tools`);
// Call a tool
const result = await client.callTool({
name: "searchSymbols",
arguments: {
query: "UserService",
limit: 10
}
});
console.log(result.content);
MCP Tool Categories
| Category | Key Tools | Use Case |
|---|---|---|
| Navigation | searchSymbols, findReferences, getCallGraph |
Find and explore code |
| Impact | analyzeImpact, analyzeChange, getAffectedTests |
Assess change risk |
| Quality | getHotspots, findDeadCodeCandidates, analyzeCoupling |
Find tech debt |
| Ownership | getOwnership, getChangeOwners, summarizePr |
Code review |
| Architecture | getArchitecture, getModuleOverview, getDecisions |
Understand structure |
| Docs | getDocsForSymbol, checkDocStaleness |
Documentation |
Tool Presets
Choose a preset based on your use case:
ckb mcp --list-presets
# Output:
# PRESET TOOLS TOKENS DESCRIPTION
# core 14 ~2k Quick navigation, search, impact analysis (default)
# review 19 ~2k Code review with ownership and PR summaries
# refactor 19 ~2k Refactoring analysis with coupling and dead code
# docs 20 ~2k Documentation-symbol linking and coverage
# federation 28 ~3k Multi-repo queries and cross-repo visibility
# ops 25 ~2k Diagnostics, daemon, webhooks, jobs
# full 76 ~9k Complete feature set
Dynamic Preset Expansion
Start with core and expand mid-session if needed:
{
"name": "expandToolset",
"arguments": {
"preset": "refactor",
"reason": "User wants to find dead code"
}
}
MCP Response Format
All MCP tools return a standardized envelope:
{
"schemaVersion": "1.0",
"data": { ... },
"meta": {
"confidence": {
"score": 0.95,
"tier": "high"
},
"provenance": {
"backends": ["scip", "git"]
},
"freshness": {
"indexAge": {
"commitsBehind": 0
}
}
},
"warnings": [],
"suggestedNextCalls": [
{
"tool": "findReferences",
"params": { "symbolId": "..." },
"reason": "See all 47 references"
}
]
}
Common Patterns
Pre-commit Hook
#!/bin/bash
# .git/hooks/pre-commit
# Check impact of staged changes
RISK=$(ckb impact diff --staged --format=json | jq -r '.data.summary.estimatedRisk')
if [ "$RISK" = "critical" ]; then
echo "Critical risk detected. Run 'ckb impact diff --staged' for details."
echo "Bypass with: git commit --no-verify"
exit 1
fi
PR Bot
# Example: GitHub Action that comments on PRs
import os
import json
from github import Github
def analyze_pr():
# Get impact analysis
impact = ckb("impact diff --base=origin/main --format=json")
# Format as markdown
risk = impact["data"]["summary"]["estimatedRisk"]
modules = impact["data"]["summary"]["affectedModules"]
comment = f"""## CKB Impact Analysis
**Risk Level:** {risk}
**Affected Modules:** {', '.join(modules)}
<details>
<summary>Changed Symbols</summary>
| Symbol | Confidence | Callers |
|--------|------------|---------|
"""
for sym in impact["data"]["changedSymbols"][:10]:
comment += f"| `{sym['name']}` | {sym['confidence']} | {sym['callerCount']} |\n"
comment += "</details>"
# Post to PR
gh = Github(os.environ["GITHUB_TOKEN"])
repo = gh.get_repo(os.environ["GITHUB_REPOSITORY"])
pr = repo.get_pull(int(os.environ["PR_NUMBER"]))
pr.create_issue_comment(comment)
VS Code Extension
// Example: VS Code extension using CKB
import * as vscode from 'vscode';
import { exec } from 'child_process';
export function activate(context: vscode.ExtensionContext) {
// Command: Show impact of current symbol
let showImpact = vscode.commands.registerCommand('ckb.showImpact', async () => {
const editor = vscode.window.activeTextEditor;
if (!editor) return;
const position = editor.selection.active;
const wordRange = editor.document.getWordRangeAtPosition(position);
const word = editor.document.getText(wordRange);
// Search for symbol
const symbols = await runCKB(`search "${word}" --format=json`);
if (!symbols.data.symbols.length) {
vscode.window.showWarningMessage(`No symbol found: ${word}`);
return;
}
// Get impact
const symbolId = symbols.data.symbols[0].id;
const impact = await runCKB(`impact "${symbolId}" --format=json`);
// Show in panel
const panel = vscode.window.createWebviewPanel(
'ckbImpact',
`Impact: ${word}`,
vscode.ViewColumn.Beside
);
panel.webview.html = formatImpactAsHTML(impact);
});
context.subscriptions.push(showImpact);
}
async function runCKB(command: string): Promise<any> {
return new Promise((resolve, reject) => {
exec(`ckb ${command}`, (error, stdout) => {
if (error) reject(error);
else resolve(JSON.parse(stdout));
});
});
}
Best Practices
1. Cache Results When Appropriate
from functools import lru_cache
import time
@lru_cache(maxsize=100)
def get_symbol_impact(symbol_id: str, cache_key: str) -> dict:
"""Cache impact results. Use index commit as cache key."""
return ckb(f'impact "{symbol_id}"')
# Get current index state for cache invalidation
status = ckb("status")
cache_key = status["data"]["index"]["commitHash"]
# Results cached until index changes
impact = get_symbol_impact("some-symbol", cache_key)
2. Use Appropriate Presets
# Don't load all 76 tools if you only need search
ckb mcp --preset=core # 14 tools, ~2k tokens
# Load more only when needed
# (MCP clients can call expandToolset dynamically)
3. Handle Stale Index Gracefully
result = ckb("impact some-symbol")
# Check freshness
if result["meta"]["freshness"]["indexAge"]["commitsBehind"] > 0:
print("Warning: Index is stale, results may be outdated")
# Check confidence
if result["meta"]["confidence"]["tier"] == "low":
print("Warning: Low confidence result")
4. Use Batch Operations
# Instead of multiple calls:
# ckb refs symbol1
# ckb refs symbol2
# ckb refs symbol3
# Use the API batch endpoint:
curl -X POST "http://localhost:8080/api/v1/symbols:batchGet" \
-d '{"ids": ["symbol1", "symbol2", "symbol3"]}'
5. Keep Index Fresh in Long-Running Services
import threading
import time
def refresh_loop(interval_seconds=300):
"""Background thread to keep index fresh."""
while True:
time.sleep(interval_seconds)
try:
ckb("index --if-stale=5m")
except Exception as e:
print(f"Refresh failed: {e}")
# Start background refresh
thread = threading.Thread(target=refresh_loop, daemon=True)
thread.start()
See Also
- API-Reference — Complete HTTP API documentation
- MCP-Tools — All 76 MCP tools with parameters
- Presets — Tool preset details
- CI-CD-Integration — CI/CD workflows and examples
- Authentication — API tokens and security