Sourcegraph Cody
AIツール
Sourcegraph Cody
概要
Sourcegraph Codyは、コードベース理解に特化したAIコーディングアシスタントです。Claude Sonnet 4、GPT-4o等の最新LLMを活用し、VS Code、JetBrains、Web版で利用できます。業界最高レベルのコードベース全体理解能力を持ち、SOC2 Type II、GDPR、CCPA準拠によりエンタープライズ環境での高いセキュリティ要求に対応します。ゼロデータ保持・トレーニング無しポリシーにより、企業の機密コードを完全に保護します。
詳細
Sourcegraph Codyは、Sourcegraphの長年のコード検索・解析技術を基盤として開発されたAIアシスタントです。2024年版では、エンタープライズレベルのコードベース全体理解、高度な検索とシンボル解析、管理者権限とアクセス制御機能を強化し、大規模開発組織での使用に最適化されています。他のAIツールと比較して、コンテキスト認識の精度と深度で群を抜いており、複雑なコードベースでの開発効率を大幅に向上させます。
主要技術特徴
- 業界最高レベルのコードベース理解: ファイル、クラス、関数間の関係を深く理解
- ゼロデータ保持ポリシー: ユーザーコードの保存・学習・第三者共有を一切行わない
- エンドツーエンド暗号化: 通信の完全暗号化によるセキュリティ確保
- 最新LLM統合: Claude Sonnet 4、GPT-4o、Gemini Pro等をサポート
- エンタープライズガバナンス: 細かなアクセス制御と管理者機能
最新機能(2024-2025年版)
- Enhanced Context Engine: コードベース、ドキュメント、サービス間の関連性理解
- Advanced Symbol Navigation: クラス、関数、変数の高精度な参照解析
- Repository-wide Intelligence: プロジェクト全体を把握した的確な提案
- Custom LLM Integration: 組織固有のLLMモデル連携サポート
- Compliance Monitoring: リアルタイムでのセキュリティ・コンプライアンス監視
メリット・デメリット
メリット
- 最強のコードベース理解: 業界で最も深いコンテキスト認識能力
- 完全なプライバシー保護: ゼロデータ保持によるコード機密性の完全確保
- エンタープライズ対応: SOC2、GDPR、CCPA準拠の包括的セキュリティ
- 柔軟なLLM選択: 複数の最新LLMを用途に応じて選択可能
- 高度な検索機能: コードベース内の効率的な情報検索
- 管理機能: 組織レベルでの詳細なアクセス制御とガバナンス
デメリット
- エンタープライズ機能中心: 個人開発者向け機能は他ツールより限定的
- 学習コスト: 高度な機能を活用するには一定の学習が必要
- 価格: エンタープライズ機能は月額15ドルと他より高価
- セットアップ複雑性: 大規模組織での導入に専門知識が必要
- 比較的新しいサービス: 市場実績はGitHub Copilot等より少ない
参考ページ
書き方の例
VS Codeでのセットアップ
# VS Code拡張機能のインストール
# 1. Extensions (Ctrl+Shift+X) を開く
# 2. "Sourcegraph Cody" を検索してインストール
# 3. Sourcegraphアカウントでサインイン
# 4. エンタープライズ環境では管理者による権限設定が必要
# コマンドラインでのインストール
code --install-extension sourcegraph.cody-ai
JetBrains IDEsでのセットアップ
# IntelliJ IDEA, PyCharm, WebStorm等での設定
# 1. Preferences > Plugins を開く
# 2. Marketplace で "Sourcegraph Cody" を検索
# 3. インストール後、IDEを再起動
# 4. Sourcegraphアカウントでログイン
# 5. エンタープライズ設定の確認
エンタープライズ環境での初期設定
# sourcegraph-config.yaml
# エンタープライズ環境でのCody設定例
enterprise:
instance_url: "https://sourcegraph.company.com"
access_token: "${SOURCEGRAPH_TOKEN}"
security:
data_retention: "zero_day"
encryption: "end_to_end"
training_opt_out: true
audit_logging: true
permissions:
repository_access:
- "company/main-app"
- "company/api-service"
- "company/shared-libraries"
user_groups:
developers:
read_access: true
chat_enabled: true
autocomplete_enabled: true
senior_developers:
read_access: true
chat_enabled: true
autocomplete_enabled: true
admin_features: true
compliance:
frameworks: ["SOC2", "GDPR", "CCPA", "HIPAA"]
monitoring: true
reporting_enabled: true
Python でのコードベース理解活用
# Sourcegraph Cody による包括的なコードベース理解例
# 複雑なコードベースでの高精度な提案
import asyncio
import aiohttp
import json
from typing import Dict, List, Optional, Union
from dataclasses import dataclass
from datetime import datetime, timedelta
@dataclass
class UserProfile:
"""ユーザープロファイルデータクラス"""
user_id: str
username: str
email: str
created_at: datetime
last_login: Optional[datetime] = None
preferences: Dict[str, Union[str, bool, int]] = None
class DatabaseManager:
"""データベース接続と操作を管理するクラス"""
def __init__(self, connection_string: str, pool_size: int = 10):
# Codyがコードベース全体を理解して適切な初期化パターンを提案
self.connection_string = connection_string
self.pool_size = pool_size
self.connection_pool = None
self.logger = self._setup_logging()
async def initialize_pool(self):
"""コネクションプール初期化"""
# Codyが既存のDB設定パターンを理解して提案
try:
import asyncpg
self.connection_pool = await asyncpg.create_pool(
self.connection_string,
min_size=1,
max_size=self.pool_size,
command_timeout=60,
server_settings={
'jit': 'off' # パフォーマンス最適化
}
)
self.logger.info("Database connection pool initialized")
except Exception as e:
self.logger.error(f"Failed to initialize connection pool: {e}")
raise
async def get_user_profile(self, user_id: str) -> Optional[UserProfile]:
"""ユーザープロファイル取得"""
# Codyがアプリケーション全体のUserProfile使用パターンを理解
if not self.connection_pool:
await self.initialize_pool()
async with self.connection_pool.acquire() as connection:
try:
query = """
SELECT user_id, username, email, created_at, last_login, preferences
FROM user_profiles
WHERE user_id = $1 AND active = true
"""
row = await connection.fetchrow(query, user_id)
if not row:
return None
# Codyがdataclass初期化パターンを提案
return UserProfile(
user_id=row['user_id'],
username=row['username'],
email=row['email'],
created_at=row['created_at'],
last_login=row['last_login'],
preferences=json.loads(row['preferences']) if row['preferences'] else {}
)
except Exception as e:
self.logger.error(f"Error fetching user profile {user_id}: {e}")
return None
async def update_user_preferences(self, user_id: str, preferences: Dict) -> bool:
"""ユーザー設定更新"""
# 既存のupdate パターンをCodyが学習して提案
if not self.connection_pool:
await self.initialize_pool()
async with self.connection_pool.acquire() as connection:
try:
# トランザクション内での安全な更新
async with connection.transaction():
# 現在の設定を取得
current_prefs = await connection.fetchval(
"SELECT preferences FROM user_profiles WHERE user_id = $1",
user_id
)
if current_prefs:
current_prefs = json.loads(current_prefs)
current_prefs.update(preferences)
else:
current_prefs = preferences
# 更新実行
await connection.execute(
"""
UPDATE user_profiles
SET preferences = $2, updated_at = CURRENT_TIMESTAMP
WHERE user_id = $1
""",
user_id, json.dumps(current_prefs)
)
# 監査ログ記録
await self._log_user_action(connection, user_id, "preferences_updated", preferences)
return True
except Exception as e:
self.logger.error(f"Error updating preferences for {user_id}: {e}")
return False
class NotificationService:
"""通知サービスクラス"""
def __init__(self, db_manager: DatabaseManager):
# Codyが依存性注入パターンを理解
self.db_manager = db_manager
self.notification_channels = {
'email': self._send_email,
'push': self._send_push_notification,
'sms': self._send_sms
}
async def send_user_notification(self, user_id: str, message: str,
channels: List[str] = None) -> Dict[str, bool]:
"""ユーザーへの通知送信"""
# Codyがユーザーの設定に基づく通知ロジックを提案
user_profile = await self.db_manager.get_user_profile(user_id)
if not user_profile:
return {"error": "User not found"}
# ユーザー設定から通知チャンネルを決定
if channels is None:
channels = user_profile.preferences.get('notification_channels', ['email'])
results = {}
# 各チャンネルで通知送信
for channel in channels:
if channel in self.notification_channels:
try:
success = await self.notification_channels[channel](user_profile, message)
results[channel] = success
except Exception as e:
self.logger.error(f"Failed to send {channel} notification to {user_id}: {e}")
results[channel] = False
else:
results[channel] = False
return results
async def _send_email(self, user_profile: UserProfile, message: str) -> bool:
"""メール送信(プライベートメソッド)"""
# Codyがメール送信パターンを組織のライブラリから学習
try:
# 組織固有のメールライブラリ使用パターンをCodyが提案
from company_libs.email import EmailSender
email_sender = EmailSender(
smtp_host=self.config['smtp_host'],
smtp_port=self.config['smtp_port'],
use_tls=True
)
await email_sender.send_async(
to_email=user_profile.email,
subject="通知",
body=message,
template_name="default_notification"
)
return True
except Exception as e:
self.logger.error(f"Email sending failed: {e}")
return False
class APIController:
"""REST API コントローラー"""
def __init__(self, db_manager: DatabaseManager, notification_service: NotificationService):
# Codyが既存のコントローラーパターンを学習
self.db_manager = db_manager
self.notification_service = notification_service
async def handle_user_settings_update(self, request_data: Dict) -> Dict:
"""ユーザー設定更新APIエンドポイント"""
# Codyがバリデーション、認証、レスポンスパターンを提案
try:
# リクエストバリデーション
user_id = request_data.get('user_id')
preferences = request_data.get('preferences', {})
if not user_id:
return {
"status": "error",
"message": "user_id is required",
"code": 400
}
# 設定更新実行
success = await self.db_manager.update_user_preferences(user_id, preferences)
if success:
# 更新通知送信
await self.notification_service.send_user_notification(
user_id,
"設定が正常に更新されました",
channels=['email']
)
return {
"status": "success",
"message": "Preferences updated successfully",
"code": 200
}
else:
return {
"status": "error",
"message": "Failed to update preferences",
"code": 500
}
except Exception as e:
self.logger.error(f"Settings update error: {e}")
return {
"status": "error",
"message": "Internal server error",
"code": 500
}
# 使用例とテスト
async def main():
"""メイン実行関数"""
# Codyがアプリケーション全体の初期化パターンを理解
db_manager = DatabaseManager("postgresql://user:pass@localhost/mydb")
await db_manager.initialize_pool()
notification_service = NotificationService(db_manager)
api_controller = APIController(db_manager, notification_service)
# テストデータで実行
test_request = {
"user_id": "user123",
"preferences": {
"notification_channels": ["email", "push"],
"language": "ja",
"theme": "dark"
}
}
result = await api_controller.handle_user_settings_update(test_request)
print(f"Update result: {result}")
if __name__ == "__main__":
asyncio.run(main())
TypeScript でのプロジェクト横断的な理解
// TypeScript プロジェクトでのCody活用例
// 複数ファイル・モジュール間の関係を深く理解
// types/user.ts
export interface User {
id: string;
username: string;
email: string;
profile: UserProfile;
permissions: Permission[];
createdAt: Date;
updatedAt: Date;
}
export interface UserProfile {
firstName: string;
lastName: string;
avatar?: string;
bio?: string;
preferences: UserPreferences;
}
export interface UserPreferences {
theme: 'light' | 'dark' | 'auto';
language: string;
notifications: NotificationSettings;
privacy: PrivacySettings;
}
export interface Permission {
resource: string;
actions: string[];
conditions?: Record<string, any>;
}
// services/UserService.ts
import { User, UserProfile, UserPreferences } from '../types/user';
import { DatabaseService } from './DatabaseService';
import { CacheService } from './CacheService';
import { NotificationService } from './NotificationService';
export class UserService {
constructor(
private db: DatabaseService,
private cache: CacheService,
private notifications: NotificationService
) {}
async getUserById(userId: string): Promise<User | null> {
// Codyが既存のキャッシングパターンを理解して提案
const cacheKey = `user:${userId}`;
// キャッシュから確認
let user = await this.cache.get<User>(cacheKey);
if (!user) {
// データベースから取得
user = await this.db.users.findById(userId, {
include: ['profile', 'permissions']
});
if (user) {
// キャッシュに保存(TTL: 1時間)
await this.cache.set(cacheKey, user, 3600);
}
}
return user;
}
async updateUserPreferences(
userId: string,
preferences: Partial<UserPreferences>
): Promise<{ success: boolean; user?: User; error?: string }> {
try {
// Codyがトランザクション処理パターンを提案
const result = await this.db.transaction(async (trx) => {
// 現在のユーザー取得
const currentUser = await trx.users.findById(userId);
if (!currentUser) {
throw new Error('User not found');
}
// 設定更新
const updatedPreferences = {
...currentUser.profile.preferences,
...preferences
};
// データベース更新
const updatedUser = await trx.users.update(userId, {
'profile.preferences': updatedPreferences,
updatedAt: new Date()
});
// 監査ログ記録
await trx.auditLogs.create({
userId,
action: 'preferences_updated',
changes: preferences,
timestamp: new Date()
});
return updatedUser;
});
// キャッシュ無効化
await this.cache.delete(`user:${userId}`);
// 通知送信(設定変更を他デバイスに通知)
await this.notifications.sendToUser(userId, {
type: 'preferences_updated',
data: preferences
});
return { success: true, user: result };
} catch (error) {
console.error('Failed to update user preferences:', error);
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error'
};
}
}
async getUsersByPermission(permission: string): Promise<User[]> {
// Codyが権限システムの関係を理解して効率的なクエリを提案
const cacheKey = `users_by_permission:${permission}`;
let users = await this.cache.get<User[]>(cacheKey);
if (!users) {
users = await this.db.users.findMany({
where: {
permissions: {
some: {
resource: permission,
actions: {
hasAny: ['read', 'write', 'admin']
}
}
}
},
include: ['profile', 'permissions']
});
// 短期キャッシュ(権限情報は頻繁に変わる可能性)
await this.cache.set(cacheKey, users, 300); // 5分
}
return users;
}
}
// controllers/UserController.ts
import { Request, Response } from 'express';
import { UserService } from '../services/UserService';
import { validateUserPreferences } from '../validators/userValidators';
import { AuthMiddleware } from '../middleware/AuthMiddleware';
export class UserController {
constructor(private userService: UserService) {}
async updatePreferences(req: Request, res: Response): Promise<void> {
try {
// Codyが認証・認可パターンを理解
const userId = req.user?.id;
if (!userId) {
res.status(401).json({ error: 'Unauthorized' });
return;
}
// リクエストバリデーション
const validationResult = validateUserPreferences(req.body);
if (!validationResult.isValid) {
res.status(400).json({
error: 'Invalid preferences data',
details: validationResult.errors
});
return;
}
// 設定更新実行
const result = await this.userService.updateUserPreferences(
userId,
req.body
);
if (result.success) {
res.json({
message: 'Preferences updated successfully',
user: result.user
});
} else {
res.status(500).json({
error: 'Failed to update preferences',
details: result.error
});
}
} catch (error) {
console.error('Controller error:', error);
res.status(500).json({ error: 'Internal server error' });
}
}
async getProfile(req: Request, res: Response): Promise<void> {
try {
const userId = req.params.userId || req.user?.id;
if (!userId) {
res.status(400).json({ error: 'User ID required' });
return;
}
// 権限チェック(自分のプロファイルまたは管理者権限)
if (userId !== req.user?.id && !req.user?.permissions.includes('admin')) {
res.status(403).json({ error: 'Insufficient permissions' });
return;
}
const user = await this.userService.getUserById(userId);
if (!user) {
res.status(404).json({ error: 'User not found' });
return;
}
// 機密情報を除外してレスポンス
const safeUser = {
id: user.id,
username: user.username,
email: user.email,
profile: user.profile,
// 権限情報は管理者のみ表示
...(req.user?.permissions.includes('admin') && { permissions: user.permissions })
};
res.json(safeUser);
} catch (error) {
console.error('Get profile error:', error);
res.status(500).json({ error: 'Internal server error' });
}
}
}
React Hooks でのコンポーネント間連携
// React + TypeScript でのCody活用例
// コンポーネント間の状態管理と副作用を深く理解
import React, { useState, useEffect, useCallback, useContext, createContext } from 'react';
import { User, UserPreferences } from '../types/user';
// Context: ユーザー状態管理
interface UserContextType {
user: User | null;
updatePreferences: (preferences: Partial<UserPreferences>) => Promise<void>;
loading: boolean;
error: string | null;
}
const UserContext = createContext<UserContextType | undefined>(undefined);
// カスタムフック: ユーザー状態管理
export const useUser = (): UserContextType => {
const context = useContext(UserContext);
if (!context) {
throw new Error('useUser must be used within UserProvider');
}
return context;
};
// プロバイダーコンポーネント
export const UserProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
// Codyが既存のAPI呼び出しパターンを理解して提案
const fetchUser = useCallback(async () => {
try {
setLoading(true);
setError(null);
const response = await fetch('/api/user/profile', {
credentials: 'include',
headers: {
'Content-Type': 'application/json',
}
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const userData = await response.json();
setUser(userData);
} catch (err) {
const errorMessage = err instanceof Error ? err.message : 'Unknown error';
setError(errorMessage);
console.error('Failed to fetch user:', err);
} finally {
setLoading(false);
}
}, []);
const updatePreferences = useCallback(async (preferences: Partial<UserPreferences>) => {
if (!user) {
throw new Error('No user loaded');
}
try {
setError(null);
const response = await fetch('/api/user/preferences', {
method: 'PUT',
credentials: 'include',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(preferences)
});
if (!response.ok) {
throw new Error(`Failed to update preferences: ${response.status}`);
}
const updatedUser = await response.json();
setUser(updatedUser.user);
} catch (err) {
const errorMessage = err instanceof Error ? err.message : 'Unknown error';
setError(errorMessage);
throw err;
}
}, [user]);
// 初期ユーザー読み込み
useEffect(() => {
fetchUser();
}, [fetchUser]);
const value: UserContextType = {
user,
updatePreferences,
loading,
error
};
return (
<UserContext.Provider value={value}>
{children}
</UserContext.Provider>
);
};
// 設定コンポーネント
const UserPreferencesForm: React.FC = () => {
const { user, updatePreferences, loading, error } = useUser();
const [formData, setFormData] = useState<Partial<UserPreferences>>({});
const [saving, setSaving] = useState(false);
// Codyがフォーム状態管理パターンを提案
useEffect(() => {
if (user?.profile.preferences) {
setFormData(user.profile.preferences);
}
}, [user]);
const handleInputChange = useCallback((
field: keyof UserPreferences,
value: any
) => {
setFormData(prev => ({
...prev,
[field]: value
}));
}, []);
const handleSubmit = useCallback(async (e: React.FormEvent) => {
e.preventDefault();
try {
setSaving(true);
await updatePreferences(formData);
// 成功通知
console.log('Preferences updated successfully');
} catch (err) {
console.error('Failed to save preferences:', err);
} finally {
setSaving(false);
}
}, [formData, updatePreferences]);
if (loading) {
return <div className="loading">ユーザー情報を読み込み中...</div>;
}
if (error) {
return <div className="error">エラー: {error}</div>;
}
if (!user) {
return <div className="error">ユーザー情報が見つかりません</div>;
}
return (
<form onSubmit={handleSubmit} className="preferences-form">
<h2>設定</h2>
{/* テーマ設定 */}
<div className="form-group">
<label htmlFor="theme">テーマ</label>
<select
id="theme"
value={formData.theme || 'light'}
onChange={(e) => handleInputChange('theme', e.target.value)}
>
<option value="light">ライト</option>
<option value="dark">ダーク</option>
<option value="auto">自動</option>
</select>
</div>
{/* 言語設定 */}
<div className="form-group">
<label htmlFor="language">言語</label>
<select
id="language"
value={formData.language || 'ja'}
onChange={(e) => handleInputChange('language', e.target.value)}
>
<option value="ja">日本語</option>
<option value="en">English</option>
<option value="zh">中文</option>
</select>
</div>
{/* 通知設定 */}
<div className="form-group">
<h3>通知設定</h3>
<label>
<input
type="checkbox"
checked={formData.notifications?.email || false}
onChange={(e) => handleInputChange('notifications', {
...formData.notifications,
email: e.target.checked
})}
/>
メール通知
</label>
<label>
<input
type="checkbox"
checked={formData.notifications?.push || false}
onChange={(e) => handleInputChange('notifications', {
...formData.notifications,
push: e.target.checked
})}
/>
プッシュ通知
</label>
</div>
{/* 保存ボタン */}
<button
type="submit"
disabled={saving}
className="save-button"
>
{saving ? '保存中...' : '設定を保存'}
</button>
</form>
);
};
// メインアプリケーション
const App: React.FC = () => {
return (
<UserProvider>
<div className="app">
<header className="app-header">
<h1>ユーザー設定アプリ</h1>
</header>
<main className="app-main">
<UserPreferencesForm />
</main>
</div>
</UserProvider>
);
};
export default App;
エンタープライズ設定とコンプライアンス
{
"sourcegraph_cody": {
"enterprise": {
"instance_url": "https://sourcegraph.company.com",
"authentication": {
"method": "sso",
"provider": "okta",
"auto_refresh": true
},
"repository_access": {
"allowed_repos": [
"company/main-application",
"company/shared-libraries",
"company/api-services"
],
"excluded_paths": [
"*/secrets/*",
"*.env",
"*/config/production/*"
]
}
},
"security": {
"data_retention": "zero_day",
"encryption": {
"in_transit": "tls_1_3",
"at_rest": "aes_256"
},
"audit_logging": {
"enabled": true,
"log_level": "comprehensive",
"retention_days": 90
},
"compliance": {
"frameworks": ["SOC2", "GDPR", "CCPA", "HIPAA"],
"data_processing_agreement": true,
"privacy_shield": true
}
},
"ai_models": {
"default": "claude-sonnet-4",
"alternatives": ["gpt-4o", "gemini-pro"],
"custom_models": {
"enabled": true,
"endpoints": [
{
"name": "company-internal-model",
"url": "https://ai.company.com/v1",
"auth_token": "${INTERNAL_AI_TOKEN}"
}
]
}
},
"permissions": {
"admin_users": [
"[email protected]",
"[email protected]"
],
"feature_access": {
"chat": ["all_users"],
"autocomplete": ["developers", "senior_developers"],
"repository_search": ["senior_developers", "architects"],
"admin_panel": ["admin_users"]
},
"rate_limits": {
"chat_requests_per_hour": 100,
"autocomplete_requests_per_minute": 60,
"search_requests_per_hour": 200
}
},
"monitoring": {
"usage_analytics": true,
"performance_monitoring": true,
"error_reporting": true,
"alerts": {
"high_error_rate": {
"threshold": "5%",
"notification": "[email protected]"
},
"quota_exceeded": {
"threshold": "90%",
"notification": "[email protected]"
}
}
}
}
}