Continue
AIツール
Continue
概要
Continueは、完全オープンソースのAIコーディングアシスタントです。VS CodeとJetBrains IDEに対応し、OpenAI、Anthropic、Ollama等の複数LLMプロバイダーを自由に選択・切り替えできます。2025年版1.0リリースにより、カスタムAIアシスタント作成・共有機能、企業向けガバナンス・セキュリティ制御、プライベートデータプレーン展開を実現し、完全なデータ制御を可能にします。
詳細
Continueは2023年にオープンソースプロジェクトとして開始され、2024年には大幅な機能強化を経て、2025年に正式版1.0をリリースしました。最新版では、Continue Hub(Docker Hub風の共有プラットフォーム)により、開発者がカスタムAIアシスタントを作成・共有できる革新的なエコシステムを構築しています。完全オープンソースにより、企業は自社データを外部に送信することなく、セルフホスト環境での高度なAI開発支援を実現できます。
主要機能
- Agent: 複雑なタスクの自動実行と多段階処理
- Chat: 自然言語によるAIとの対話型コーディング支援
- Autocomplete: リアルタイムコード補完
- Edit: 既存コードの変換・リファクタリング
- カスタムLLM統合: 任意のLLMモデル・プロバイダー対応
2025年版1.0の新機能
- Continue Hub: カスタムAIアシスタント共有プラットフォーム
- エンタープライズガバナンス: 細かなアクセス制御と使用量管理
- セルフホスト対応: 完全プライベート環境での展開
- プラグイン機能: カスタムツール・サービス統合API
- 多言語サポート: 日本語を含む多言語インターフェース
メリット・デメリット
メリット
- 完全オープンソース: 透明性とカスタマイズ性の確保、ベンダーロックイン回避
- 最強のカスタマイズ性: 設定ファイルによる詳細なカスタマイズ
- 完全データ制御: セルフホスト可能、外部データ送信なし
- マルチLLM対応: OpenAI、Anthropic、Ollama、ローカルモデル等自由選択
- コスト効率: Solo版無料、エンタープライズ版も競合より安価
- コミュニティ主導: 活発な開発コミュニティと豊富な拡張
デメリット
- 複雑なセットアップ: 設定ファイル編集等、技術的知識が必要
- 学習コスト: 高度な機能活用に時間を要する
- サポート体制: オープンソースのため公式サポートが限定的
- 安定性: 新しいプロジェクトのため、商用サービスより不安定な場合
- UI/UX: 商用製品ほど洗練されていないインターフェース
参考ページ
書き方の例
VS Codeでのセットアップ
# VS Code拡張機能のインストール
# 1. Extensions (Ctrl+Shift+X) を開く
# 2. "Continue" を検索してインストール
# 3. 右下のContinueアイコンをクリック
# 4. 設定ファイル(config.json)の編集
# コマンドラインでのインストール
code --install-extension continue.continue
基本的な設定ファイル(config.json)
{
"models": [
{
"title": "GPT-4o",
"provider": "openai",
"model": "gpt-4o",
"apiKey": "your-openai-api-key",
"contextLength": 128000
},
{
"title": "Claude Sonnet 4",
"provider": "anthropic",
"model": "claude-3-5-sonnet-20241022",
"apiKey": "your-anthropic-api-key",
"contextLength": 200000
},
{
"title": "Llama 3.1 Local",
"provider": "ollama",
"model": "llama3.1:8b",
"apiBase": "http://localhost:11434"
}
],
"tabAutocompleteModel": {
"title": "Codestral",
"provider": "mistral",
"model": "codestral-latest",
"apiKey": "your-mistral-api-key"
},
"allowAnonymousTelemetry": false,
"embeddingsProvider": {
"provider": "transformers.js",
"model": "Xenova/all-MiniLM-L6-v2"
}
}
Python でのプロジェクト分析とリファクタリング
# Continue を使った大規模Pythonプロジェクトの分析・リファクタリング
# Agent機能による包括的なコード改善
import asyncio
import aiohttp
import json
import logging
from typing import Dict, List, Optional, Union, Any
from dataclasses import dataclass, field
from datetime import datetime, timedelta
from pathlib import Path
# Continueに「このクラスを分析してメモリ効率を改善して」と依頼
@dataclass
class PerformanceOptimizedUserProfile:
"""メモリ効率を最適化したユーザープロファイル"""
user_id: str
username: str = field(compare=False) # 比較処理から除外
email: str = field(repr=False) # repr表示から除外
preferences: Dict[str, Any] = field(default_factory=dict)
_cache: Dict[str, Any] = field(default_factory=dict, init=False, repr=False)
def __post_init__(self):
# Continueがパフォーマンス最適化パターンを提案
# 文字列インターンによるメモリ使用量削減
self.user_id = intern(self.user_id)
self.username = intern(self.username)
@property
def display_name(self) -> str:
"""キャッシュされた表示名"""
if 'display_name' not in self._cache:
self._cache['display_name'] = f"{self.username} ({self.user_id})"
return self._cache['display_name']
def clear_cache(self):
"""キャッシュクリア"""
self._cache.clear()
class OptimizedDatabaseManager:
"""パフォーマンス最適化されたデータベースマネージャー"""
def __init__(self, connection_string: str, max_pool_size: int = 20):
# Continueが高性能データベース接続パターンを提案
self.connection_string = connection_string
self.max_pool_size = max_pool_size
self.connection_pool = None
self.query_cache = {} # クエリ結果キャッシュ
self.prepared_statements = {} # プリペアードステートメント
# ロギング設定
self.logger = logging.getLogger(__name__)
self.performance_logger = logging.getLogger(f"{__name__}.performance")
async def initialize_pool(self):
"""最適化されたコネクションプール初期化"""
try:
import asyncpg
# Continueが高性能DB設定を提案
self.connection_pool = await asyncpg.create_pool(
self.connection_string,
min_size=2,
max_size=self.max_pool_size,
command_timeout=30,
max_queries=50000,
max_inactive_connection_lifetime=300,
server_settings={
'jit': 'off',
'shared_preload_libraries': 'pg_stat_statements',
'track_activity_query_size': '2048'
}
)
# プリペアードステートメント事前作成
await self._prepare_common_statements()
self.logger.info("Optimized connection pool initialized")
except Exception as e:
self.logger.error(f"Failed to initialize pool: {e}")
raise
async def _prepare_common_statements(self):
"""よく使用されるクエリのプリペアードステートメント作成"""
# Continueが使用頻度の高いクエリパターンを分析して提案
common_queries = {
'get_user_by_id': """
SELECT user_id, username, email, preferences, created_at
FROM user_profiles
WHERE user_id = $1 AND active = true
""",
'update_user_preferences': """
UPDATE user_profiles
SET preferences = preferences || $2, updated_at = CURRENT_TIMESTAMP
WHERE user_id = $1
RETURNING user_id, preferences
""",
'get_users_by_activity': """
SELECT user_id, username, last_login
FROM user_profiles
WHERE last_login > $1
ORDER BY last_login DESC
LIMIT $2
"""
}
async with self.connection_pool.acquire() as connection:
for name, query in common_queries.items():
self.prepared_statements[name] = await connection.prepare(query)
async def get_user_optimized(self, user_id: str) -> Optional[PerformanceOptimizedUserProfile]:
"""最適化されたユーザー取得"""
# キャッシュ確認
cache_key = f"user:{user_id}"
if cache_key in self.query_cache:
cache_data = self.query_cache[cache_key]
# キャッシュ有効期限確認(5分)
if datetime.now() - cache_data['timestamp'] < timedelta(minutes=5):
self.performance_logger.debug(f"Cache hit for user {user_id}")
return cache_data['data']
start_time = datetime.now()
try:
async with self.connection_pool.acquire() as connection:
# プリペアードステートメント使用
stmt = self.prepared_statements['get_user_by_id']
row = await stmt.fetchrow(user_id)
if not row:
return None
# 最適化されたオブジェクト作成
user_profile = PerformanceOptimizedUserProfile(
user_id=row['user_id'],
username=row['username'],
email=row['email'],
preferences=row['preferences'] or {}
)
# 結果をキャッシュ
self.query_cache[cache_key] = {
'data': user_profile,
'timestamp': datetime.now()
}
# パフォーマンス測定
execution_time = (datetime.now() - start_time).total_seconds()
self.performance_logger.info(f"User fetch took {execution_time:.3f}s")
return user_profile
except Exception as e:
self.logger.error(f"Error fetching user {user_id}: {e}")
return None
async def batch_update_preferences(self, updates: List[Dict[str, Any]]) -> List[str]:
"""バッチ処理による設定更新(高性能)"""
# Continueがバッチ処理最適化パターンを提案
updated_users = []
async with self.connection_pool.acquire() as connection:
async with connection.transaction():
stmt = self.prepared_statements['update_user_preferences']
# バッチ実行
for update in updates:
user_id = update['user_id']
preferences = update['preferences']
try:
result = await stmt.fetchrow(user_id, preferences)
if result:
updated_users.append(result['user_id'])
# キャッシュ無効化
cache_key = f"user:{user_id}"
if cache_key in self.query_cache:
del self.query_cache[cache_key]
except Exception as e:
self.logger.error(f"Failed to update user {user_id}: {e}")
continue
self.logger.info(f"Batch updated {len(updated_users)} users")
return updated_users
class AdvancedAPIController:
"""高度なAPIコントローラー(Continue最適化版)"""
def __init__(self, db_manager: OptimizedDatabaseManager):
self.db_manager = db_manager
self.request_cache = {}
self.rate_limiter = {}
async def handle_bulk_user_operation(self, request_data: Dict[str, Any]) -> Dict[str, Any]:
"""バルクユーザー操作の高性能処理"""
# Continueが大量データ処理パターンを提案
operation = request_data.get('operation')
user_ids = request_data.get('user_ids', [])
if not operation or not user_ids:
return {
"status": "error",
"message": "Operation and user_ids are required",
"code": 400
}
if len(user_ids) > 1000: # 大量処理制限
return {
"status": "error",
"message": "Maximum 1000 users per batch",
"code": 400
}
start_time = datetime.now()
try:
if operation == "get_profiles":
# 並列処理による高速取得
tasks = [
self.db_manager.get_user_optimized(user_id)
for user_id in user_ids
]
results = await asyncio.gather(*tasks, return_exceptions=True)
# 成功結果のみフィルタ
successful_results = [
{
"user_id": result.user_id,
"username": result.username,
"display_name": result.display_name
}
for result in results
if isinstance(result, PerformanceOptimizedUserProfile)
]
processing_time = (datetime.now() - start_time).total_seconds()
return {
"status": "success",
"data": successful_results,
"metadata": {
"total_requested": len(user_ids),
"successful": len(successful_results),
"processing_time": f"{processing_time:.3f}s"
}
}
elif operation == "update_preferences":
# バッチ更新処理
preferences = request_data.get('preferences', {})
updates = [
{"user_id": user_id, "preferences": preferences}
for user_id in user_ids
]
updated_users = await self.db_manager.batch_update_preferences(updates)
processing_time = (datetime.now() - start_time).total_seconds()
return {
"status": "success",
"data": {
"updated_users": updated_users,
"updated_count": len(updated_users)
},
"metadata": {
"processing_time": f"{processing_time:.3f}s"
}
}
else:
return {
"status": "error",
"message": f"Unknown operation: {operation}",
"code": 400
}
except Exception as e:
self.logger.error(f"Bulk operation error: {e}")
return {
"status": "error",
"message": "Internal server error",
"code": 500
}
# Continue Agent機能使用例
async def performance_analysis_agent():
"""Continueエージェントによるパフォーマンス分析"""
# エージェントへの指示例:
"""
以下のタスクを自動実行してください:
1. このPythonコードベースのパフォーマンスボトルネックを特定
2. メモリ使用量の最適化提案
3. データベースクエリの効率化
4. 非同期処理の改善点
5. 包括的なパフォーマンステストコード生成
6. 最適化後のベンチマーク比較レポート作成
"""
# Continueエージェントが以下を自動実行:
# ✓ コードベース全体のプロファイリング
# ✓ ボトルネック箇所の特定とレポート生成
# ✓ 最適化コードの提案と実装
# ✓ テストコードの自動生成
# ✓ パフォーマンス比較レポート作成
print("Performance analysis completed by Continue Agent")
if __name__ == "__main__":
# 使用例
async def main():
db_manager = OptimizedDatabaseManager("postgresql://user:pass@localhost/db")
await db_manager.initialize_pool()
api_controller = AdvancedAPIController(db_manager)
# テストリクエスト
test_request = {
"operation": "get_profiles",
"user_ids": [f"user{i}" for i in range(100)] # 100ユーザーのバッチ処理
}
result = await api_controller.handle_bulk_user_operation(test_request)
print(f"Bulk operation result: {result}")
# エージェント分析実行
await performance_analysis_agent()
asyncio.run(main())
TypeScript でのカスタムLLM統合
// TypeScript での Continue カスタムLLM統合例
// 組織固有のAIモデル・サービス連携
interface CustomLLMConfig {
name: string;
endpoint: string;
apiKey: string;
model: string;
contextLength: number;
capabilities: string[];
}
interface ContinueConfig {
models: CustomLLMConfig[];
customCommands: CustomCommand[];
contextProviders: ContextProvider[];
}
// カスタムコマンド定義
interface CustomCommand {
name: string;
description: string;
prompt: string;
model?: string;
}
// 企業固有のContinue設定
const enterpriseConfig: ContinueConfig = {
models: [
{
name: "Company Internal GPT",
endpoint: "https://ai.company.com/v1/chat/completions",
apiKey: process.env.COMPANY_AI_API_KEY!,
model: "company-gpt-4-turbo",
contextLength: 128000,
capabilities: ["chat", "edit", "autocomplete"]
},
{
name: "Security Focused Model",
endpoint: "https://secure-ai.company.com/api",
apiKey: process.env.SECURE_AI_KEY!,
model: "security-llm-v2",
contextLength: 64000,
capabilities: ["security-review", "vulnerability-scan"]
},
{
name: "Code Review Specialist",
endpoint: "https://codereview-ai.company.com/analyze",
apiKey: process.env.CODE_REVIEW_KEY!,
model: "code-reviewer-pro",
contextLength: 200000,
capabilities: ["code-review", "refactor-suggest"]
}
],
customCommands: [
{
name: "security-audit",
description: "Perform comprehensive security audit on selected code",
prompt: `Analyze the following code for security vulnerabilities:
1. SQL injection risks
2. XSS vulnerabilities
3. Authentication bypasses
4. Data exposure risks
5. Input validation issues
Provide specific fixes and security best practices:
{{{ input }}}`,
model: "Security Focused Model"
},
{
name: "performance-optimize",
description: "Optimize code for performance",
prompt: `Analyze and optimize the following code for performance:
1. Memory usage optimization
2. Algorithm efficiency improvements
3. Database query optimization
4. Caching strategies
5. Async/await best practices
Provide optimized code with performance metrics:
{{{ input }}}`,
model: "Company Internal GPT"
},
{
name: "enterprise-refactor",
description: "Refactor code following company standards",
prompt: `Refactor the following code according to our enterprise standards:
1. Follow company coding conventions
2. Implement proper error handling
3. Add comprehensive logging
4. Ensure type safety
5. Add unit tests
6. Document complex logic
Company standards context: {{{ company_standards }}}
Code to refactor:
{{{ input }}}`,
model: "Code Review Specialist"
}
],
contextProviders: [
{
name: "company-docs",
description: "Company internal documentation",
query: "search-company-docs",
type: "retrieval"
},
{
name: "jira-tickets",
description: "Related JIRA tickets",
query: "get-related-tickets",
type: "api"
}
]
};
// React での高度なコンポーネント開発
// Continue Agent による包括的なコンポーネント実装
import React, { useState, useEffect, useCallback, useMemo } from 'react';
import { debounce } from 'lodash';
interface AdvancedSearchProps {
onSearch: (query: string, filters: SearchFilters) => Promise<SearchResult[]>;
placeholder?: string;
initialFilters?: SearchFilters;
maxResults?: number;
}
interface SearchFilters {
category?: string;
dateRange?: DateRange;
tags?: string[];
sortBy?: 'relevance' | 'date' | 'popularity';
includeArchived?: boolean;
}
interface SearchResult {
id: string;
title: string;
description: string;
category: string;
tags: string[];
createdAt: Date;
relevanceScore: number;
}
// Continue に「高性能検索コンポーネントを実装して」と依頼
const AdvancedSearchComponent: React.FC<AdvancedSearchProps> = ({
onSearch,
placeholder = "検索キーワードを入力...",
initialFilters = {},
maxResults = 50
}) => {
const [query, setQuery] = useState<string>('');
const [filters, setFilters] = useState<SearchFilters>(initialFilters);
const [results, setResults] = useState<SearchResult[]>([]);
const [loading, setLoading] = useState<boolean>(false);
const [error, setError] = useState<string | null>(null);
// Continue がパフォーマンス最適化パターンを提案
// デバウンス検索実装
const debouncedSearch = useMemo(
() => debounce(async (searchQuery: string, searchFilters: SearchFilters) => {
if (!searchQuery.trim()) {
setResults([]);
return;
}
try {
setLoading(true);
setError(null);
const searchResults = await onSearch(searchQuery, searchFilters);
setResults(searchResults.slice(0, maxResults));
} catch (err) {
setError(err instanceof Error ? err.message : 'Search failed');
setResults([]);
} finally {
setLoading(false);
}
}, 300),
[onSearch, maxResults]
);
// 検索クエリ・フィルター変更時の自動検索
useEffect(() => {
debouncedSearch(query, filters);
// クリーンアップ
return () => {
debouncedSearch.cancel();
};
}, [query, filters, debouncedSearch]);
// フィルター更新関数
const updateFilter = useCallback(<K extends keyof SearchFilters>(
key: K,
value: SearchFilters[K]
) => {
setFilters(prev => ({
...prev,
[key]: value
}));
}, []);
// 検索結果のハイライト表示
const highlightSearchTerms = useCallback((text: string, searchQuery: string): string => {
if (!searchQuery.trim()) return text;
const regex = new RegExp(`(${searchQuery.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi');
return text.replace(regex, '<mark>$1</mark>');
}, []);
return (
<div className="advanced-search">
{/* 検索入力フィールド */}
<div className="search-input-section">
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder={placeholder}
className="search-input"
/>
{loading && <div className="search-spinner">検索中...</div>}
</div>
{/* フィルターセクション */}
<div className="search-filters">
<select
value={filters.category || ''}
onChange={(e) => updateFilter('category', e.target.value || undefined)}
>
<option value="">すべてのカテゴリ</option>
<option value="documents">ドキュメント</option>
<option value="code">コード</option>
<option value="issues">課題</option>
<option value="discussions">ディスカッション</option>
</select>
<select
value={filters.sortBy || 'relevance'}
onChange={(e) => updateFilter('sortBy', e.target.value as SearchFilters['sortBy'])}
>
<option value="relevance">関連度順</option>
<option value="date">日付順</option>
<option value="popularity">人気順</option>
</select>
<label>
<input
type="checkbox"
checked={filters.includeArchived || false}
onChange={(e) => updateFilter('includeArchived', e.target.checked)}
/>
アーカイブ済みを含む
</label>
</div>
{/* エラー表示 */}
{error && (
<div className="search-error">
エラー: {error}
</div>
)}
{/* 検索結果表示 */}
<div className="search-results">
{results.length === 0 && !loading && query.trim() && (
<div className="no-results">
検索結果が見つかりませんでした
</div>
)}
{results.map((result) => (
<div key={result.id} className="search-result-item">
<h3
className="result-title"
dangerouslySetInnerHTML={{
__html: highlightSearchTerms(result.title, query)
}}
/>
<p
className="result-description"
dangerouslySetInnerHTML={{
__html: highlightSearchTerms(result.description, query)
}}
/>
<div className="result-metadata">
<span className="result-category">{result.category}</span>
<span className="result-date">
{result.createdAt.toLocaleDateString('ja-JP')}
</span>
<span className="result-score">
関連度: {(result.relevanceScore * 100).toFixed(1)}%
</span>
</div>
<div className="result-tags">
{result.tags.map(tag => (
<span key={tag} className="result-tag">
{tag}
</span>
))}
</div>
</div>
))}
</div>
{/* 結果数表示 */}
{results.length > 0 && (
<div className="search-summary">
{results.length} 件の結果
{results.length === maxResults && ' (制限により一部のみ表示)'}
</div>
)}
</div>
);
};
export default AdvancedSearchComponent;
カスタムツール・プラグイン開発
// Continue カスタムツール・プラグイン開発例
// 組織固有のワークフロー自動化
// Continue設定ファイル(config.json)での定義
const customToolsConfig = {
"experimental": {
"modelRoles": {
"architect": "gpt-4o",
"security": "security-focused-model",
"reviewer": "code-review-specialist"
}
},
"customCommands": [
{
"name": "create-feature",
"description": "Create complete feature with tests and documentation",
"prompt": "Create a complete feature implementation including:\n1. Core functionality\n2. Unit tests\n3. Integration tests\n4. API documentation\n5. User documentation\n\nFeature requirements:\n{{{ input }}}"
}
],
"tools": [
{
"type": "function",
"function": {
"name": "analyze_codebase",
"description": "Analyze codebase structure and dependencies",
"parameters": {
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "Path to analyze"
},
"depth": {
"type": "integer",
"description": "Analysis depth (1-5)"
}
},
"required": ["path"]
}
}
}
]
};
// Node.js でのカスタムツール実装
class ContinueCustomTool {
constructor(workspacePath) {
this.workspacePath = workspacePath;
this.logger = console;
}
// コードベース分析ツール
async analyzeCodebase(path, depth = 3) {
try {
const fs = require('fs').promises;
const pathModule = require('path');
const analysis = {
structure: {},
dependencies: {},
metrics: {},
issues: []
};
// ディレクトリ構造分析
analysis.structure = await this.analyzeDirectoryStructure(
pathModule.join(this.workspacePath, path),
depth
);
// 依存関係分析
analysis.dependencies = await this.analyzeDependencies(path);
// コードメトリクス計算
analysis.metrics = await this.calculateCodeMetrics(path);
// 潜在的問題検出
analysis.issues = await this.detectIssues(path);
return analysis;
} catch (error) {
this.logger.error('Codebase analysis failed:', error);
throw error;
}
}
async analyzeDirectoryStructure(dirPath, maxDepth, currentDepth = 0) {
const fs = require('fs').promises;
const pathModule = require('path');
if (currentDepth >= maxDepth) return null;
try {
const items = await fs.readdir(dirPath);
const structure = {};
for (const item of items) {
const itemPath = pathModule.join(dirPath, item);
const stats = await fs.stat(itemPath);
if (stats.isDirectory()) {
// 除外ディレクトリのスキップ
if (['node_modules', '.git', 'dist', 'build'].includes(item)) {
continue;
}
structure[item] = {
type: 'directory',
children: await this.analyzeDirectoryStructure(
itemPath,
maxDepth,
currentDepth + 1
)
};
} else {
// ファイル情報
structure[item] = {
type: 'file',
size: stats.size,
extension: pathModule.extname(item),
modified: stats.mtime
};
}
}
return structure;
} catch (error) {
this.logger.warn(`Failed to analyze directory ${dirPath}:`, error);
return null;
}
}
async analyzeDependencies(projectPath) {
const fs = require('fs').promises;
const pathModule = require('path');
const dependencies = {
direct: {},
dev: {},
peer: {},
vulnerabilities: []
};
try {
// package.json 分析
const packageJsonPath = pathModule.join(this.workspacePath, projectPath, 'package.json');
const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'));
dependencies.direct = packageJson.dependencies || {};
dependencies.dev = packageJson.devDependencies || {};
dependencies.peer = packageJson.peerDependencies || {};
// 脆弱性スキャン(npm audit相当)
dependencies.vulnerabilities = await this.scanVulnerabilities(projectPath);
} catch (error) {
this.logger.warn('Dependency analysis failed:', error);
}
return dependencies;
}
async calculateCodeMetrics(projectPath) {
const fs = require('fs').promises;
const pathModule = require('path');
const metrics = {
totalFiles: 0,
totalLines: 0,
codeLines: 0,
commentLines: 0,
blankLines: 0,
complexity: 0,
duplicateBlocks: []
};
// TypeScript/JavaScript ファイルの分析
const jsFiles = await this.findFiles(
pathModule.join(this.workspacePath, projectPath),
/\.(ts|tsx|js|jsx)$/
);
for (const filePath of jsFiles) {
try {
const content = await fs.readFile(filePath, 'utf8');
const lines = content.split('\n');
metrics.totalFiles++;
metrics.totalLines += lines.length;
// 行の分類
for (const line of lines) {
const trimmed = line.trim();
if (!trimmed) {
metrics.blankLines++;
} else if (trimmed.startsWith('//') || trimmed.startsWith('/*')) {
metrics.commentLines++;
} else {
metrics.codeLines++;
}
}
// 循環複雑度計算(簡易版)
metrics.complexity += this.calculateComplexity(content);
} catch (error) {
this.logger.warn(`Failed to analyze file ${filePath}:`, error);
}
}
return metrics;
}
calculateComplexity(code) {
// 簡易循環複雑度計算
const complexityKeywords = [
'if', 'else', 'while', 'for', 'switch', 'case',
'catch', 'throw', '&&', '||', '?'
];
let complexity = 1; // 基本値
for (const keyword of complexityKeywords) {
const regex = new RegExp(`\\b${keyword}\\b`, 'g');
const matches = code.match(regex);
if (matches) {
complexity += matches.length;
}
}
return complexity;
}
async detectIssues(projectPath) {
const issues = [];
// 一般的な問題パターンの検出
const problemPatterns = [
{
name: 'console.log残留',
pattern: /console\.log\(/g,
severity: 'warning',
description: '本番環境にconsole.logが残っています'
},
{
name: 'ハードコードされたURL',
pattern: /https?:\/\/[^\s"']+/g,
severity: 'info',
description: 'ハードコードされたURLが検出されました'
},
{
name: 'TODO/FIXMEコメント',
pattern: /(TODO|FIXME|HACK):/gi,
severity: 'info',
description: '未完了のタスクがあります'
}
];
// ファイル検索と問題検出
const codeFiles = await this.findFiles(
pathModule.join(this.workspacePath, projectPath),
/\.(ts|tsx|js|jsx|py|java|go|rb)$/
);
for (const filePath of codeFiles) {
try {
const content = await fs.readFile(filePath, 'utf8');
for (const pattern of problemPatterns) {
const matches = content.match(pattern.pattern);
if (matches) {
issues.push({
file: filePath,
type: pattern.name,
severity: pattern.severity,
description: pattern.description,
occurrences: matches.length
});
}
}
} catch (error) {
this.logger.warn(`Failed to scan file ${filePath}:`, error);
}
}
return issues;
}
async findFiles(directory, pattern) {
const fs = require('fs').promises;
const pathModule = require('path');
const files = [];
async function searchRecursive(dir) {
try {
const items = await fs.readdir(dir);
for (const item of items) {
const fullPath = pathModule.join(dir, item);
const stats = await fs.stat(fullPath);
if (stats.isDirectory()) {
// 除外ディレクトリのスキップ
if (!['node_modules', '.git', 'dist', 'build'].includes(item)) {
await searchRecursive(fullPath);
}
} else if (pattern.test(item)) {
files.push(fullPath);
}
}
} catch (error) {
// ディレクトリアクセスエラーは無視
}
}
await searchRecursive(directory);
return files;
}
}
// Continue プラグインの登録
module.exports = {
ContinueCustomTool,
customToolsConfig
};
エンタープライズ向けセルフホスト設定
# docker-compose.yml
# Continue エンタープライズ版のセルフホスト環境
version: '3.8'
services:
continue-server:
image: continue/continue-server:enterprise-1.0
container_name: continue-enterprise
ports:
- "8080:8080"
environment:
- NODE_ENV=production
- DATABASE_URL=postgresql://continue:${DB_PASSWORD}@postgres:5432/continue_enterprise
- REDIS_URL=redis://redis:6379
- JWT_SECRET=${JWT_SECRET}
- ENCRYPTION_KEY=${ENCRYPTION_KEY}
- CORS_ORIGINS=https://continue.company.com
volumes:
- ./config:/app/config
- ./logs:/app/logs
- ./models:/app/models
depends_on:
- postgres
- redis
restart: unless-stopped
networks:
- continue-network
postgres:
image: postgres:15-alpine
container_name: continue-postgres
environment:
- POSTGRES_DB=continue_enterprise
- POSTGRES_USER=continue
- POSTGRES_PASSWORD=${DB_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
restart: unless-stopped
networks:
- continue-network
redis:
image: redis:7-alpine
container_name: continue-redis
command: redis-server --requirepass ${REDIS_PASSWORD}
volumes:
- redis_data:/data
restart: unless-stopped
networks:
- continue-network
nginx:
image: nginx:alpine
container_name: continue-nginx
ports:
- "443:443"
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
- ./ssl:/etc/nginx/ssl
depends_on:
- continue-server
restart: unless-stopped
networks:
- continue-network
volumes:
postgres_data:
redis_data:
networks:
continue-network:
driver: bridge
# config/enterprise.yaml
# エンタープライズ設定ファイル
enterprise:
license_key: "${CONTINUE_ENTERPRISE_LICENSE}"
organization: "Web Reference Corp"
security:
data_retention: "zero_day"
encryption:
at_rest: true
in_transit: true
key_rotation_days: 90
authentication:
providers:
- type: "saml"
name: "Corporate SSO"
metadata_url: "https://sso.company.com/metadata"
- type: "ldap"
host: "ldap.company.com"
port: 389
base_dn: "dc=company,dc=com"
audit:
enabled: true
log_level: "detailed"
retention_days: 365
export_format: ["json", "csv"]
models:
providers:
- name: "OpenAI"
type: "openai"
api_key: "${OPENAI_API_KEY}"
models: ["gpt-4o", "gpt-4-turbo"]
- name: "Anthropic"
type: "anthropic"
api_key: "${ANTHROPIC_API_KEY}"
models: ["claude-3-5-sonnet-20241022"]
- name: "Internal AI"
type: "custom"
endpoint: "https://ai.company.com/v1"
api_key: "${INTERNAL_AI_KEY}"
models: ["company-gpt-4"]
features:
chat: true
autocomplete: true
edit: true
agent: true
custom_commands: true
permissions:
admin_users:
- "[email protected]"
- "[email protected]"
user_groups:
developers:
features: ["chat", "autocomplete", "edit"]
rate_limits:
requests_per_hour: 1000
senior_developers:
features: ["chat", "autocomplete", "edit", "agent"]
rate_limits:
requests_per_hour: 2000
architects:
features: ["chat", "autocomplete", "edit", "agent", "custom_commands"]
rate_limits:
requests_per_hour: 5000
monitoring:
metrics:
enabled: true
prometheus: true
endpoint: "/metrics"
alerts:
slack_webhook: "${SLACK_WEBHOOK_URL}"
email_recipients: ["[email protected]"]
health_checks:
interval_seconds: 30
timeout_seconds: 10