Skip to content

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