UNPKG

aios-core

Version:

Synkra AIOS: AI-Orchestrated System for Full Stack Development - Core Framework

291 lines (251 loc) 9.57 kB
#!/usr/bin/env node // ============================================================================= // AIOS Capability Detection Module // ============================================================================= // Detects Claude Code runtime capabilities for token optimization decisions. // Runs at session initialization (not per-turn). // // Story: TOK-2 (Deferred/Search Capability-Aware Loading) // ADR-7: Capability gate por runtime // // Usage: // node .aios-core/data/capability-detection.js // // Output: // .aios/runtime-capabilities.json // ============================================================================= const fs = require('fs'); const path = require('path'); const os = require('os'); const PROJECT_ROOT = path.resolve(__dirname, '../..'); const OUTPUT_PATH = path.join(PROJECT_ROOT, '.aios', 'runtime-capabilities.json'); function detectToolSearch() { // Check Claude Code cached features for tool search availability const claudeJsonPath = path.join(os.homedir(), '.claude.json'); try { const config = JSON.parse(fs.readFileSync(claudeJsonPath, 'utf8')); const features = config.cachedGrowthBookFeatures || {}; return { available: features.tengu_mcp_tool_search === true, source: 'cachedGrowthBookFeatures.tengu_mcp_tool_search', detectionMethod: 'claude-json-feature-flag' }; } catch { return { available: false, source: 'detection-failed', detectionMethod: 'claude-json-feature-flag' }; } } function detectDeferLoading() { // defer_loading is API-only (Python SDK mcp_toolset), NOT available in Claude Code CLI return { available: false, reason: 'defer_loading is API-only (Python SDK). Not exposed in Claude Code CLI.', source: 'ADR-7 / Codex CRITICO-1' }; } function detectProjectMcps() { const mcpJsonPath = path.join(PROJECT_ROOT, '.mcp.json'); try { const config = JSON.parse(fs.readFileSync(mcpJsonPath, 'utf8')); const servers = config.mcpServers || {}; return Object.entries(servers).map(([name, cfg]) => ({ name, type: cfg.type || 'command', scope: 'project', source: '.mcp.json' })); } catch { return []; } } function detectGlobalMcps() { // Docker MCP Gateway catalog const dockerMcpConfigPath = path.join(os.homedir(), '.docker', 'mcp', 'config.yaml'); const servers = []; try { const content = fs.readFileSync(dockerMcpConfigPath, 'utf8'); // Simple YAML top-level key parsing (keys at indent 0 followed by colon) const lines = content.split('\n'); for (const line of lines) { const match = line.match(/^([a-zA-Z0-9_-]+):/); if (match) { servers.push({ name: match[1], type: 'docker-gateway', scope: 'global', source: '~/.docker/mcp/config.yaml' }); } } } catch { // Docker MCP not configured — not an error } // Direct MCPs in Claude Code (playwright, etc.) // These are registered via `claude mcp add` at user scope // Detection: check ~/.claude/settings.json or known conventions const settingsPath = path.join(os.homedir(), '.claude', 'settings.json'); try { const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8')); if (settings.mcpServers) { for (const [name, cfg] of Object.entries(settings.mcpServers)) { servers.push({ name, type: cfg.type || 'command', scope: 'global-settings', source: '~/.claude/settings.json' }); } } } catch { // No global settings MCPs } return servers; } function detectDockerGateway() { const dockerMcpDir = path.join(os.homedir(), '.docker', 'mcp'); const exists = fs.existsSync(dockerMcpDir); return { available: exists, configPath: exists ? dockerMcpDir : null, detectionMethod: 'filesystem-check' }; } function loadToolRegistry() { const registryPath = path.join(PROJECT_ROOT, '.aios-core', 'data', 'tool-registry.yaml'); try { const content = fs.readFileSync(registryPath, 'utf8'); // Count tools by tier (simple regex parsing) const tier1 = (content.match(/tier: 1/g) || []).length; const tier2 = (content.match(/tier: 2/g) || []).length; const tier3 = (content.match(/tier: 3/g) || []).length; // 2-pass parsing: collect all fields per tool, then classify // (essential: may appear before mcp_server: in YAML) const tools = {}; let currentTool = null; // Pass 1: collect all fields for (const line of content.split('\n')) { const toolMatch = line.match(/^ {2}([a-zA-Z0-9_-]+):$/); if (toolMatch) { currentTool = toolMatch[1]; tools[currentTool] = {}; continue; } if (!currentTool) continue; const tierMatch = line.match(/^\s+tier:\s*(\d)/); if (tierMatch) { tools[currentTool].tier = parseInt(tierMatch[1]); continue; } const mcpMatch = line.match(/^\s+mcp_server:\s*(.+)/); if (mcpMatch) { tools[currentTool].mcpServer = mcpMatch[1].trim(); continue; } const essentialMatch = line.match(/^\s+essential:\s*(true|false)/); if (essentialMatch) { tools[currentTool].essential = essentialMatch[1] === 'true'; continue; } } // Pass 2: classify Tier 3 tools with essential flag const essential = []; const nonEssential = []; for (const [name, fields] of Object.entries(tools)) { if (fields.tier !== 3 || fields.essential === undefined) continue; const scope = fields.mcpServer === 'project' ? 'project' : 'global'; const entry = { name, scope }; if (fields.essential) { essential.push(entry); } else { nonEssential.push(entry); } } return { available: true, totalTools: tier1 + tier2 + tier3, tier1Count: tier1, tier2Count: tier2, tier3Count: tier3, essential, nonEssential }; } catch { return { available: false, totalTools: 0, tier1Count: 0, tier2Count: 0, tier3Count: 0, essential: [], nonEssential: [] }; } } function determineStrategy(toolSearch, deferLoading, dockerGateway) { // ADR-7 Strategy Hierarchy: // 1. Best case: Tool Search auto-mode → deferred MCP schemas automatically // 2. Fallback 1: MCP discipline — disable non-essential MCP servers // 3. Fallback 2: CLAUDE.md guidance — instruct to prefer native tools if (toolSearch.available) { return { primary: 'tool-search-auto', description: 'Claude Code Tool Search is active. Tier 3 MCP tools are automatically deferred via tool_search.', fallbacks: ['mcp-discipline', 'claudemd-guidance'] }; } if (dockerGateway.available) { return { primary: 'mcp-discipline', description: 'Tool Search not available. Using MCP discipline: disable non-essential servers in .mcp.json.', fallbacks: ['claudemd-guidance'] }; } return { primary: 'claudemd-guidance', description: 'Neither Tool Search nor Docker Gateway available. Using CLAUDE.md guidance for tool selection priority.', fallbacks: [] }; } function run() { const timestamp = new Date().toISOString(); const toolSearch = detectToolSearch(); const deferLoading = detectDeferLoading(); const projectMcps = detectProjectMcps(); const globalMcps = detectGlobalMcps(); const dockerGateway = detectDockerGateway(); const toolRegistry = loadToolRegistry(); const strategy = determineStrategy(toolSearch, deferLoading, dockerGateway); const capabilities = { version: '1.0.0', generatedAt: timestamp, generatedBy: 'capability-detection.js', story: 'TOK-2', methodology: { toolSearchLatency: 'Managed internally by Claude Code — not programmatically measurable. Guidance-level enforcement via CLAUDE.md.', mcpCountUnit: 'servers (not individual tools). TOK-1.5 baseline uses tool count (e.g., Apify = 7 tools = 1 server).' }, runtime: { toolSearch, deferLoading, dockerGateway }, mcpServers: { project: projectMcps, global: globalMcps, totalCount: projectMcps.length + globalMcps.length, countUnit: 'servers' }, toolRegistry, strategy, // Essential/non-essential derived from tool-registry.yaml (single source of truth) essentialServers: toolRegistry.essential.length > 0 ? toolRegistry.essential : [{ name: 'nogic', scope: 'project' }, { name: 'code-graph', scope: 'project' }], nonEssentialServers: toolRegistry.nonEssential.length > 0 ? toolRegistry.nonEssential : [] }; // Ensure output directory exists const outputDir = path.dirname(OUTPUT_PATH); if (!fs.existsSync(outputDir)) { fs.mkdirSync(outputDir, { recursive: true }); } fs.writeFileSync(OUTPUT_PATH, JSON.stringify(capabilities, null, 2)); console.log(`✅ Runtime capabilities detected and saved to ${OUTPUT_PATH}`); console.log(` Strategy: ${strategy.primary} — ${strategy.description}`); console.log(` Tool Search: ${toolSearch.available ? 'AVAILABLE' : 'NOT AVAILABLE'}`); console.log(` MCPs: ${projectMcps.length} project + ${globalMcps.length} global = ${projectMcps.length + globalMcps.length} total`); console.log(` Tool Registry: ${toolRegistry.totalTools} tools (T1:${toolRegistry.tier1Count} T2:${toolRegistry.tier2Count} T3:${toolRegistry.tier3Count})`); return capabilities; } // Execute if (require.main === module) { run(); } module.exports = { run, detectToolSearch, detectProjectMcps, detectGlobalMcps, detectDockerGateway };