UNPKG

aios-core

Version:

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

1,011 lines (886 loc) 29.5 kB
/** * Squad Designer * * Analyzes documentation and generates squad blueprints * with intelligent agent and task recommendations. * * @module squad-designer * @version 1.0.0 * @see Story SQS-9: Squad Designer */ const fs = require('fs').promises; const path = require('path'); const yaml = require('js-yaml'); /** * Default output path for blueprints * @constant {string} */ const DEFAULT_DESIGNS_PATH = './squads/.designs'; /** * Minimum confidence threshold for recommendations * @constant {number} */ const MIN_CONFIDENCE_THRESHOLD = 0.5; /** * Keywords that indicate workflow actions * @constant {string[]} */ const ACTION_KEYWORDS = [ 'create', 'add', 'new', 'generate', 'build', 'update', 'edit', 'modify', 'change', 'patch', 'delete', 'remove', 'cancel', 'archive', 'get', 'fetch', 'retrieve', 'list', 'search', 'find', 'query', 'process', 'handle', 'manage', 'execute', 'run', 'validate', 'verify', 'check', 'approve', 'reject', 'send', 'notify', 'alert', 'email', 'publish', 'import', 'export', 'sync', 'migrate', 'transform', 'login', 'logout', 'authenticate', 'authorize', 'upload', 'download', 'save', 'load', ]; /** * Keywords that indicate integrations * @constant {string[]} */ const INTEGRATION_KEYWORDS = [ 'api', 'rest', 'graphql', 'webhook', 'endpoint', 'database', 'db', 'sql', 'nosql', 'redis', 'postgres', 'mysql', 'mongodb', 'aws', 'azure', 'gcp', 'cloud', 's3', 'lambda', 'stripe', 'paypal', 'payment', 'gateway', 'slack', 'discord', 'email', 'sms', 'twilio', 'oauth', 'jwt', 'auth0', 'firebase', 'github', 'gitlab', 'bitbucket', 'docker', 'kubernetes', 'k8s', ]; /** * Keywords that indicate stakeholder roles * @constant {string[]} */ const ROLE_KEYWORDS = [ 'user', 'admin', 'administrator', 'manager', 'owner', 'customer', 'client', 'buyer', 'seller', 'vendor', 'developer', 'engineer', 'devops', 'qa', 'tester', 'analyst', 'designer', 'architect', 'operator', 'support', 'agent', 'representative', ]; /** * Error codes for SquadDesignerError * @enum {string} */ const DesignerErrorCodes = { NO_DOCUMENTATION: 'NO_DOCUMENTATION', PARSE_ERROR: 'PARSE_ERROR', EMPTY_ANALYSIS: 'EMPTY_ANALYSIS', BLUEPRINT_EXISTS: 'BLUEPRINT_EXISTS', INVALID_BLUEPRINT: 'INVALID_BLUEPRINT', SAVE_ERROR: 'SAVE_ERROR', }; /** * Custom error class for Squad Designer operations * @extends Error */ class SquadDesignerError extends Error { /** * Create a SquadDesignerError * @param {string} code - Error code from DesignerErrorCodes enum * @param {string} message - Human-readable error message * @param {string} [suggestion] - Suggested fix for the error */ constructor(code, message, suggestion) { super(message); this.name = 'SquadDesignerError'; this.code = code; this.suggestion = suggestion || ''; if (Error.captureStackTrace) { Error.captureStackTrace(this, SquadDesignerError); } } /** * Create error for no documentation provided * @returns {SquadDesignerError} */ static noDocumentation() { return new SquadDesignerError( DesignerErrorCodes.NO_DOCUMENTATION, 'No documentation provided for analysis', 'Provide documentation via --docs flag or paste text interactively', ); } /** * Create error for parse failure * @param {string} filePath - Path that failed to parse * @param {string} reason - Parse failure reason * @returns {SquadDesignerError} */ static parseError(filePath, reason) { return new SquadDesignerError( DesignerErrorCodes.PARSE_ERROR, `Failed to parse documentation: ${filePath} - ${reason}`, 'Check file format (supported: .md, .yaml, .yml, .json, .txt)', ); } /** * Create error for empty analysis result * @returns {SquadDesignerError} */ static emptyAnalysis() { return new SquadDesignerError( DesignerErrorCodes.EMPTY_ANALYSIS, 'No domain concepts could be extracted from documentation', 'Provide more detailed documentation with clear entities and workflows', ); } /** * Create error for existing blueprint * @param {string} blueprintPath - Path where blueprint exists * @returns {SquadDesignerError} */ static blueprintExists(blueprintPath) { return new SquadDesignerError( DesignerErrorCodes.BLUEPRINT_EXISTS, `Blueprint already exists at ${blueprintPath}`, 'Use --force to overwrite or choose a different output path', ); } } /** * Squad Designer class * Analyzes documentation and generates squad blueprints */ class SquadDesigner { /** * Create a SquadDesigner * @param {Object} options - Designer options * @param {string} [options.designsPath] - Path to designs directory */ constructor(options = {}) { this.designsPath = options.designsPath || DEFAULT_DESIGNS_PATH; } // =========================================================================== // DOCUMENTATION COLLECTION // =========================================================================== /** * Collect and normalize documentation from various sources * @param {Object} options - Collection options * @param {string|string[]} [options.docs] - File paths or text content * @param {string} [options.text] - Direct text input * @param {string} [options.domain] - Domain hint * @returns {Promise<Object>} Normalized documentation */ async collectDocumentation(options) { const sources = []; // Handle file paths if (options.docs) { const paths = Array.isArray(options.docs) ? options.docs : options.docs.split(',').map(p => p.trim()); for (const filePath of paths) { try { const content = await this.readDocumentationFile(filePath); sources.push({ type: 'file', path: filePath, content, }); } catch (error) { throw SquadDesignerError.parseError(filePath, error.message); } } } // Handle direct text input if (options.text) { sources.push({ type: 'text', path: null, content: options.text, }); } if (sources.length === 0) { throw SquadDesignerError.noDocumentation(); } return { sources, domainHint: options.domain || null, mergedContent: sources.map(s => s.content).join('\n\n---\n\n'), }; } /** * Read and parse a documentation file * @param {string} filePath - Path to file * @returns {Promise<string>} File content as text */ async readDocumentationFile(filePath) { const content = await fs.readFile(filePath, 'utf-8'); const ext = path.extname(filePath).toLowerCase(); switch (ext) { case '.yaml': case '.yml': // Convert YAML to readable text try { const parsed = yaml.load(content); return this.yamlToText(parsed); } catch { return content; // Return raw if parse fails } case '.json': // Convert JSON to readable text try { const parsed = JSON.parse(content); return this.jsonToText(parsed); } catch { return content; } case '.md': case '.txt': default: return content; } } /** * Convert YAML object to readable text * @param {Object} obj - Parsed YAML object * @param {number} [depth=0] - Current depth for indentation * @returns {string} Text representation */ yamlToText(obj, depth = 0) { if (typeof obj !== 'object' || obj === null) { return String(obj); } const indent = ' '.repeat(depth); const lines = []; for (const [key, value] of Object.entries(obj)) { if (Array.isArray(value)) { lines.push(`${indent}${key}:`); for (const item of value) { if (typeof item === 'object') { lines.push(`${indent} - ${this.yamlToText(item, depth + 2)}`); } else { lines.push(`${indent} - ${item}`); } } } else if (typeof value === 'object' && value !== null) { lines.push(`${indent}${key}:`); lines.push(this.yamlToText(value, depth + 1)); } else { lines.push(`${indent}${key}: ${value}`); } } return lines.join('\n'); } /** * Convert JSON object to readable text * @param {Object} obj - Parsed JSON object * @returns {string} Text representation */ jsonToText(obj) { return this.yamlToText(obj); // Reuse YAML converter } // =========================================================================== // DOMAIN ANALYSIS // =========================================================================== /** * Analyze documentation and extract domain concepts * @param {Object} documentation - Normalized documentation from collectDocumentation * @returns {Object} Analysis result with entities, workflows, integrations, stakeholders */ analyzeDomain(documentation) { const content = documentation.mergedContent.toLowerCase(); const originalContent = documentation.mergedContent; const analysis = { domain: this.extractDomain(originalContent, documentation.domainHint), entities: this.extractEntities(originalContent), workflows: this.extractWorkflows(content, originalContent), integrations: this.extractIntegrations(content), stakeholders: this.extractStakeholders(content), }; // Validate we extracted something useful if ( analysis.entities.length === 0 && analysis.workflows.length === 0 ) { throw SquadDesignerError.emptyAnalysis(); } return analysis; } /** * Extract domain name from content * @param {string} content - Original content * @param {string|null} hint - Domain hint if provided * @returns {string} Domain name */ extractDomain(content, hint) { if (hint) { return this.toDomainName(hint); } // Try to extract from title/heading const titleMatch = content.match(/^#\s+(.+)$/m); if (titleMatch) { return this.toDomainName(titleMatch[1]); } // Try to extract from "name:" in YAML const nameMatch = content.match(/name:\s*(.+)/i); if (nameMatch) { return this.toDomainName(nameMatch[1]); } return 'custom-domain'; } /** * Convert text to domain name format * @param {string} text - Input text * @returns {string} Kebab-case domain name */ toDomainName(text) { return text .toLowerCase() .replace(/[^a-z0-9\s-]/g, '') .replace(/\s+/g, '-') .replace(/-+/g, '-') .replace(/^-|-$/g, '') .substring(0, 50); } /** * Extract entities (nouns, concepts) from content * @param {string} content - Original content (preserves case) * @returns {string[]} List of entities */ extractEntities(content) { const entities = new Set(); // Find capitalized words (likely entities) const capitalizedPattern = /\b([A-Z][a-z]+(?:[A-Z][a-z]+)*)\b/g; let match; while ((match = capitalizedPattern.exec(content)) !== null) { const word = match[1]; // Filter out common non-entity words if (!this.isCommonWord(word) && word.length > 2) { entities.add(word); } } // Find words in backticks or quotes (often entities in docs) const quotedPattern = /[`"']([A-Za-z][A-Za-z0-9_]+)[`"']/g; while ((match = quotedPattern.exec(content)) !== null) { const word = match[1]; if (!this.isCommonWord(word) && word.length > 2) { entities.add(this.toTitleCase(word)); } } return Array.from(entities).slice(0, 20); // Limit to top 20 } /** * Check if word is a common non-entity word * @param {string} word - Word to check * @returns {boolean} True if common word */ isCommonWord(word) { const commonWords = new Set([ 'The', 'This', 'That', 'These', 'Those', 'What', 'When', 'Where', 'Which', 'How', 'Why', 'Who', 'All', 'Any', 'Some', 'Each', 'Every', 'Both', 'Few', 'More', 'Most', 'Other', 'Such', 'No', 'Not', 'Only', 'Same', 'Than', 'Too', 'Very', 'Just', 'But', 'And', 'For', 'With', 'From', 'About', 'Into', 'Through', 'During', 'Before', 'After', 'Above', 'Below', 'Between', 'Under', 'Again', 'Further', 'Then', 'Once', 'Here', 'There', 'True', 'False', 'Null', 'None', 'Yes', 'No', 'Example', 'Note', 'Warning', 'Error', 'Success', 'Failure', 'Status', 'Type', 'Name', 'Value', 'Data', 'File', 'Path', 'String', 'Number', 'Boolean', 'Array', 'Object', 'Function', 'Class', 'Method', 'Property', 'Parameter', 'Return', 'Input', 'Output', 'Request', 'Response', 'Result', 'Config', 'Options', 'Settings', ]); return commonWords.has(word); } /** * Convert string to title case * @param {string} str - Input string * @returns {string} Title case string */ toTitleCase(str) { return str.charAt(0).toUpperCase() + str.slice(1); } /** * Extract workflows from content * @param {string} lowerContent - Lowercase content * @param {string} originalContent - Original content * @returns {string[]} List of workflow names */ extractWorkflows(lowerContent, originalContent) { const workflows = new Set(); // Find action + noun patterns for (const action of ACTION_KEYWORDS) { const pattern = new RegExp(`\\b${action}[\\s-]+([a-z]+)`, 'gi'); let match; while ((match = pattern.exec(lowerContent)) !== null) { const noun = match[1]; if (noun.length > 2 && !this.isStopWord(noun)) { workflows.add(`${action}-${noun}`); } } } // Find numbered steps or bullet points with actions const stepPattern = /(?:^|\n)\s*(?:\d+\.|[-*])\s*([A-Za-z]+)\s+(?:the\s+)?([a-z]+)/gi; let match; while ((match = stepPattern.exec(originalContent)) !== null) { const verb = match[1].toLowerCase(); const noun = match[2].toLowerCase(); if (ACTION_KEYWORDS.includes(verb) && !this.isStopWord(noun)) { workflows.add(`${verb}-${noun}`); } } return Array.from(workflows).slice(0, 15); // Limit to top 15 } /** * Check if word is a stop word * @param {string} word - Word to check * @returns {boolean} True if stop word */ isStopWord(word) { const stopWords = new Set([ 'the', 'a', 'an', 'is', 'are', 'was', 'were', 'be', 'been', 'being', 'have', 'has', 'had', 'do', 'does', 'did', 'will', 'would', 'could', 'should', 'may', 'might', 'must', 'shall', 'can', 'need', 'dare', 'to', 'of', 'in', 'for', 'on', 'with', 'at', 'by', 'from', 'as', 'it', 'its', 'this', 'that', 'these', 'those', 'all', 'each', 'every', ]); return stopWords.has(word.toLowerCase()); } /** * Extract integrations from content * @param {string} content - Lowercase content * @returns {string[]} List of integrations */ extractIntegrations(content) { const integrations = new Set(); for (const keyword of INTEGRATION_KEYWORDS) { if (content.includes(keyword)) { integrations.add(this.toTitleCase(keyword)); } } // Find API endpoints mentioned const apiPattern = /(?:api|endpoint)[:\s]+([a-z/_-]+)/gi; let match; while ((match = apiPattern.exec(content)) !== null) { integrations.add(`API: ${match[1]}`); } return Array.from(integrations).slice(0, 10); } /** * Extract stakeholders from content * @param {string} content - Lowercase content * @returns {string[]} List of stakeholders */ extractStakeholders(content) { const stakeholders = new Set(); for (const role of ROLE_KEYWORDS) { const pattern = new RegExp(`\\b${role}s?\\b`, 'gi'); if (pattern.test(content)) { stakeholders.add(this.toTitleCase(role)); } } return Array.from(stakeholders).slice(0, 10); } // =========================================================================== // RECOMMENDATION GENERATION // =========================================================================== /** * Generate agent recommendations based on analysis * @param {Object} analysis - Domain analysis result * @returns {Array} Recommended agents with confidence scores */ generateAgentRecommendations(analysis) { const agents = []; const usedWorkflows = new Set(); // Group workflows by main action category const workflowGroups = this.groupWorkflowsByCategory(analysis.workflows); for (const [category, workflows] of Object.entries(workflowGroups)) { if (workflows.length === 0) continue; const agentId = `${analysis.domain}-${category}`; const commands = workflows.map(w => w.replace(/-/g, '-')); // Calculate confidence based on workflow clarity const confidence = Math.min( 0.95, 0.6 + (workflows.length * 0.05) + (commands.length > 3 ? 0.1 : 0), ); agents.push({ id: this.toKebabCase(agentId), role: this.generateAgentRole(category, workflows, analysis.domain), commands: commands.slice(0, 6), // Max 6 commands per agent confidence: Math.round(confidence * 100) / 100, user_added: false, user_modified: false, }); workflows.forEach(w => usedWorkflows.add(w)); } // If we have entities but no clear workflows, create a generic manager if (agents.length === 0 && analysis.entities.length > 0) { const mainEntity = analysis.entities[0].toLowerCase(); agents.push({ id: `${mainEntity}-manager`, role: `Manages ${mainEntity} lifecycle and operations`, commands: [`create-${mainEntity}`, `update-${mainEntity}`, `delete-${mainEntity}`, `list-${mainEntity}s`], confidence: 0.65, user_added: false, user_modified: false, }); } return agents; } /** * Group workflows by category * @param {string[]} workflows - List of workflows * @returns {Object} Grouped workflows */ groupWorkflowsByCategory(workflows) { const groups = { manager: [], processor: [], handler: [], }; for (const workflow of workflows) { const [action] = workflow.split('-'); if (['create', 'update', 'delete', 'add', 'remove', 'edit'].includes(action)) { groups.manager.push(workflow); } else if (['process', 'transform', 'migrate', 'sync', 'import', 'export'].includes(action)) { groups.processor.push(workflow); } else { groups.handler.push(workflow); } } // Remove empty groups return Object.fromEntries( Object.entries(groups).filter(([, v]) => v.length > 0), ); } /** * Generate agent role description * @param {string} category - Agent category * @param {string[]} workflows - Agent workflows * @param {string} domain - Domain name * @returns {string} Role description */ generateAgentRole(category, workflows, domain) { const domainTitle = domain.split('-').map(w => this.toTitleCase(w)).join(' '); switch (category) { case 'manager': return `Manages ${domainTitle} resources and lifecycle operations`; case 'processor': return `Processes and transforms ${domainTitle} data`; case 'handler': return `Handles ${domainTitle} events and operations`; default: return `Manages ${domainTitle} ${category} operations`; } } /** * Convert string to kebab-case * @param {string} str - Input string * @returns {string} Kebab-case string */ toKebabCase(str) { return str .toLowerCase() .replace(/[^a-z0-9]+/g, '-') .replace(/^-|-$/g, ''); } /** * Generate task recommendations based on analysis and agents * @param {Object} analysis - Domain analysis result * @param {Array} agents - Recommended agents * @returns {Array} Recommended tasks with confidence scores */ generateTaskRecommendations(analysis, agents) { const tasks = []; for (const agent of agents) { for (const command of agent.commands) { const taskName = `${command}.md`; const entrada = this.generateTaskEntrada(command, analysis); const saida = this.generateTaskSaida(command, analysis); // Calculate confidence based on entrada/saida clarity const confidence = Math.min( 0.95, 0.5 + (entrada.length * 0.1) + (saida.length * 0.1), ); tasks.push({ name: taskName.replace('.md', ''), agent: agent.id, entrada, saida, confidence: Math.round(confidence * 100) / 100, }); } } return tasks; } /** * Generate task entrada (inputs) * @param {string} command - Command name * @param {Object} analysis - Domain analysis * @returns {string[]} Input parameters */ generateTaskEntrada(command, analysis) { const inputs = []; const [action, ...rest] = command.split('-'); const subject = rest.join('_'); switch (action) { case 'create': case 'add': inputs.push(`${subject}_data`); if (analysis.stakeholders.length > 0) { inputs.push('created_by'); } break; case 'update': case 'edit': case 'modify': inputs.push(`${subject}_id`); inputs.push('updates'); break; case 'delete': case 'remove': inputs.push(`${subject}_id`); break; case 'get': case 'fetch': case 'retrieve': inputs.push(`${subject}_id`); break; case 'list': case 'search': case 'find': inputs.push('filters'); inputs.push('pagination'); break; case 'process': case 'transform': inputs.push('source_data'); inputs.push('options'); break; default: inputs.push(`${subject}_id`); inputs.push('options'); } return inputs; } /** * Generate task saida (outputs) * @param {string} command - Command name * @param {Object} _analysis - Domain analysis (reserved for future use) * @returns {string[]} Output parameters */ generateTaskSaida(command, _analysis) { const outputs = []; const [action, ...rest] = command.split('-'); const subject = rest.join('_'); switch (action) { case 'create': case 'add': outputs.push(`${subject}_id`); outputs.push('status'); break; case 'update': case 'edit': case 'modify': outputs.push(`updated_${subject}`); outputs.push('changelog'); break; case 'delete': case 'remove': outputs.push('success'); outputs.push('deleted_at'); break; case 'get': case 'fetch': case 'retrieve': outputs.push(subject); break; case 'list': case 'search': case 'find': outputs.push(`${subject}_list`); outputs.push('total_count'); break; case 'process': case 'transform': outputs.push('result_data'); outputs.push('metrics'); break; default: outputs.push('result'); outputs.push('status'); } return outputs; } // =========================================================================== // BLUEPRINT GENERATION // =========================================================================== /** * Generate complete blueprint * @param {Object} options - Blueprint options * @param {Object} options.analysis - Domain analysis * @param {Object} options.recommendations - Agent and task recommendations * @param {Object} options.metadata - Blueprint metadata * @param {Object} [options.userAdjustments] - User modifications * @returns {Object} Complete squad blueprint */ generateBlueprint(options) { const { analysis, recommendations, metadata, userAdjustments } = options; // Calculate overall confidence const agentConfidences = recommendations.agents.map(a => a.confidence); const taskConfidences = recommendations.tasks.map(t => t.confidence); const allConfidences = [...agentConfidences, ...taskConfidences]; const overallConfidence = allConfidences.length > 0 ? allConfidences.reduce((a, b) => a + b, 0) / allConfidences.length : 0.5; // Determine template based on recommendations const template = this.determineTemplate(recommendations); return { squad: { name: `${analysis.domain}-squad`, description: `Squad for ${analysis.domain.replace(/-/g, ' ')} management`, domain: analysis.domain, }, analysis: { entities: analysis.entities, workflows: analysis.workflows, integrations: analysis.integrations, stakeholders: analysis.stakeholders, }, recommendations: { agents: recommendations.agents, tasks: recommendations.tasks, template, config_mode: 'extend', }, metadata: { created_at: metadata.created_at || new Date().toISOString(), source_docs: metadata.source_docs || [], user_adjustments: userAdjustments?.count || 0, overall_confidence: Math.round(overallConfidence * 100) / 100, }, }; } /** * Determine best template based on recommendations * @param {Object} recommendations - Recommendations * @returns {string} Template name */ determineTemplate(recommendations) { const hasDataProcessing = recommendations.tasks.some(t => t.name.includes('process') || t.name.includes('transform') || t.name.includes('import') || t.name.includes('export'), ); if (hasDataProcessing) { return 'etl'; } if (recommendations.tasks.length === 0) { return 'agent-only'; } return 'basic'; } // =========================================================================== // BLUEPRINT PERSISTENCE // =========================================================================== /** * Save blueprint to file * @param {Object} blueprint - Squad blueprint * @param {string} [outputPath] - Output path (optional) * @param {Object} [options] - Save options * @param {boolean} [options.force] - Overwrite existing * @returns {Promise<string>} Path to saved file */ async saveBlueprint(blueprint, outputPath, options = {}) { const designsDir = outputPath || this.designsPath; // Ensure designs directory exists await fs.mkdir(designsDir, { recursive: true }); const filename = `${blueprint.squad.name}-design.yaml`; const filePath = path.join(designsDir, filename); // Check if file exists if (!options.force) { try { await fs.access(filePath); throw SquadDesignerError.blueprintExists(filePath); } catch (error) { if (error.code !== 'ENOENT') { throw error; } } } // Generate YAML content const yamlContent = this.blueprintToYaml(blueprint); // Write file await fs.writeFile(filePath, yamlContent, 'utf-8'); return filePath; } /** * Convert blueprint to YAML string * @param {Object} blueprint - Blueprint object * @returns {string} YAML content */ blueprintToYaml(blueprint) { const header = `# Squad Design Blueprint # Generated by *design-squad # Source: ${blueprint.metadata.source_docs.join(', ') || 'Interactive input'} # Created: ${blueprint.metadata.created_at} `; return header + yaml.dump(blueprint, { indent: 2, lineWidth: 100, noRefs: true, sortKeys: false, }); } /** * Load blueprint from file * @param {string} blueprintPath - Path to blueprint file * @returns {Promise<Object>} Loaded blueprint */ async loadBlueprint(blueprintPath) { try { const content = await fs.readFile(blueprintPath, 'utf-8'); return yaml.load(content); } catch (error) { throw new SquadDesignerError( DesignerErrorCodes.INVALID_BLUEPRINT, `Failed to load blueprint: ${error.message}`, 'Check that the blueprint file exists and is valid YAML', ); } } /** * Validate blueprint structure * @param {Object} blueprint - Blueprint to validate * @returns {Object} Validation result { valid, errors } */ validateBlueprint(blueprint) { const errors = []; // Check required top-level keys if (!blueprint.squad) { errors.push('Missing required key: squad'); } else { if (!blueprint.squad.name) errors.push('Missing squad.name'); if (!blueprint.squad.domain) errors.push('Missing squad.domain'); } if (!blueprint.recommendations) { errors.push('Missing required key: recommendations'); } else { if (!Array.isArray(blueprint.recommendations.agents)) { errors.push('recommendations.agents must be an array'); } if (!Array.isArray(blueprint.recommendations.tasks)) { errors.push('recommendations.tasks must be an array'); } } if (!blueprint.metadata) { errors.push('Missing required key: metadata'); } return { valid: errors.length === 0, errors, }; } } module.exports = { SquadDesigner, SquadDesignerError, DesignerErrorCodes, DEFAULT_DESIGNS_PATH, MIN_CONFIDENCE_THRESHOLD, ACTION_KEYWORDS, INTEGRATION_KEYWORDS, ROLE_KEYWORDS, };