UNPKG

aios-core

Version:

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

413 lines (372 loc) 11.4 kB
/** * Executor Assignment Module - Dynamic assignment of executors and quality gates * * DETERMINISTIC: Assignment is based on keyword matching against story content. * No AI involvement in executor selection. * * PRD Reference: AIOS v2.0 "Projeto Bob" - Section 5 (Dynamic Executor Assignment) * * Responsibilities: * - Detect story type from content using keyword matching * - Assign executor based on work type * - Assign quality gate (always different from executor) * - Provide quality gate tools for each assignment * * @module core/orchestration/executor-assignment * @version 1.0.0 */ /** * @typedef {Object} ExecutorAssignment * @property {string} executor - The assigned executor agent (e.g., '@dev') * @property {string} quality_gate - The quality gate reviewer (always different from executor) * @property {string[]} quality_gate_tools - Tools available for quality gate review */ /** * @typedef {Object} StoryTypeConfig * @property {string[]} keywords - Keywords that identify this story type * @property {string} executor - Default executor for this type * @property {string} quality_gate - Default quality gate for this type * @property {string[]} quality_gate_tools - Tools for quality gate review */ /** * Executor assignment table mapping work types to executors and quality gates * @constant {Object.<string, StoryTypeConfig>} */ const EXECUTOR_ASSIGNMENT_TABLE = { // General code: features, logic, handlers, services code_general: { keywords: [ 'feature', 'logic', 'handler', 'service', 'controller', 'function', 'class', 'module', 'implement', 'api', 'endpoint', 'crud', 'business', ], executor: '@dev', quality_gate: '@architect', quality_gate_tools: ['architecture_review', 'code_review', 'pattern_validation'], }, // Database: schemas, RLS, migrations, queries database: { keywords: [ 'schema', 'table', 'migration', 'rls', 'query', 'index', 'constraint', 'foreign_key', 'database', 'sql', 'supabase', 'postgres', 'column', 'relation', ], executor: '@data-engineer', quality_gate: '@dev', quality_gate_tools: ['schema_validation', 'migration_review', 'rls_test'], }, // Infrastructure: CI/CD, deploy, environments infrastructure: { keywords: [ 'ci/cd', 'cicd', 'deploy', 'environment', 'docker', 'kubernetes', 'terraform', 'pipeline', 'infrastructure', 'aws', 'gcp', 'azure', 'nginx', 'devops', ], executor: '@devops', quality_gate: '@architect', quality_gate_tools: ['infrastructure_review', 'security_scan', 'config_validation'], }, // UI/UX: components, design, interface ui_ux: { keywords: [ 'component', 'ui', 'ux', 'design', 'interface', 'layout', 'styling', 'responsive', 'accessibility', 'a11y', 'css', 'tailwind', 'figma', 'wireframe', ], executor: '@ux-design-expert', quality_gate: '@dev', quality_gate_tools: ['accessibility_check', 'design_review', 'component_validation'], }, // Research: investigation, analysis, POC research: { keywords: [ 'research', 'investigate', 'analyze', 'study', 'compare', 'evaluate', 'poc', 'proof', 'concept', 'benchmark', 'assessment', 'exploration', ], executor: '@analyst', quality_gate: '@pm', quality_gate_tools: ['research_validation', 'findings_review'], }, // Architecture: design decisions, patterns, scalability architecture: { keywords: [ 'architecture', 'design_decision', 'pattern', 'scalability', 'refactor_major', 'system_design', 'adr', 'rfc', 'technical_decision', 'microservice', 'monolith', ], executor: '@architect', quality_gate: '@pm', quality_gate_tools: ['architecture_review', 'impact_analysis'], }, }; /** * Default assignment when no specific type is detected * @constant {ExecutorAssignment} */ const DEFAULT_ASSIGNMENT = { executor: '@dev', quality_gate: '@architect', quality_gate_tools: ['code_review', 'pattern_validation'], }; /** * Detects the story type based on content analysis using keyword matching * * @param {string} storyContent - The story content (title, description, acceptance criteria) * @returns {string} The detected story type key (e.g., 'code_general', 'database') * * @example * const storyType = detectStoryType('Implement user authentication handler'); * // Returns: 'code_general' * * @example * const storyType = detectStoryType('Create RLS policies for user table'); * // Returns: 'database' */ function detectStoryType(storyContent) { if (!storyContent || typeof storyContent !== 'string') { return 'code_general'; // Default type } const normalizedContent = storyContent.toLowerCase(); const scores = {}; // Calculate scores for each type based on keyword matches for (const [typeKey, config] of Object.entries(EXECUTOR_ASSIGNMENT_TABLE)) { let score = 0; for (const keyword of config.keywords) { const keywordLower = keyword.toLowerCase(); // Count occurrences of keyword const regex = new RegExp(`\\b${keywordLower.replace(/[/\\-]/g, '[/\\\\-]?')}\\b`, 'gi'); const matches = normalizedContent.match(regex); if (matches) { score += matches.length; } } scores[typeKey] = score; } // Find the type with highest score let maxScore = 0; let detectedType = 'code_general'; for (const [typeKey, score] of Object.entries(scores)) { if (score > maxScore) { maxScore = score; detectedType = typeKey; } } return detectedType; } /** * Assigns executor and quality gate based on story type * * @param {string} storyType - The story type key from detectStoryType() * @returns {ExecutorAssignment} The executor assignment with executor, quality_gate, and tools * @throws {Error} If executor would be the same as quality gate (should never happen with valid table) * * @example * const assignment = assignExecutor('database'); * // Returns: { * // executor: '@data-engineer', * // quality_gate: '@dev', * // quality_gate_tools: ['schema_validation', 'migration_review', 'rls_test'] * // } */ function assignExecutor(storyType) { const config = EXECUTOR_ASSIGNMENT_TABLE[storyType]; if (!config) { console.warn(`[ExecutorAssignment] Unknown story type: ${storyType}, using default`); return { ...DEFAULT_ASSIGNMENT }; } const assignment = { executor: config.executor, quality_gate: config.quality_gate, quality_gate_tools: [...config.quality_gate_tools], }; // Validation: executor must be different from quality gate if (assignment.executor === assignment.quality_gate) { throw new Error( `[ExecutorAssignment] Invalid configuration: executor (${assignment.executor}) ` + `cannot be the same as quality_gate (${assignment.quality_gate}) for type "${storyType}"`, ); } return assignment; } /** * Assigns executor and quality gate based on story content (combines detect + assign) * * @param {string} storyContent - The story content to analyze * @returns {ExecutorAssignment} The executor assignment * * @example * const assignment = assignExecutorFromContent(` * # Story: Create user authentication * Implement JWT-based authentication handler with refresh tokens * `); * // Returns: { executor: '@dev', quality_gate: '@architect', quality_gate_tools: [...] } */ function assignExecutorFromContent(storyContent) { const storyType = detectStoryType(storyContent); return assignExecutor(storyType); } /** * Validates that a story has valid executor assignment * * @param {Object} story - Story object with executor fields * @param {string} story.executor - The assigned executor * @param {string} story.quality_gate - The quality gate reviewer * @param {string[]} [story.quality_gate_tools] - Quality gate tools * @returns {Object} Validation result with isValid flag and errors array * * @example * const result = validateExecutorAssignment({ * executor: '@dev', * quality_gate: '@architect', * quality_gate_tools: ['code_review'] * }); * // Returns: { isValid: true, errors: [] } */ function validateExecutorAssignment(story) { const errors = []; // Check required fields if (!story.executor) { errors.push('Missing required field: executor'); } if (!story.quality_gate) { errors.push('Missing required field: quality_gate'); } if (!story.quality_gate_tools || !Array.isArray(story.quality_gate_tools)) { errors.push('Missing or invalid field: quality_gate_tools (must be array)'); } else if (story.quality_gate_tools.length === 0) { errors.push('quality_gate_tools cannot be empty'); } // Check executor != quality_gate if (story.executor && story.quality_gate && story.executor === story.quality_gate) { errors.push(`Executor (${story.executor}) cannot be the same as quality_gate (${story.quality_gate})`); } // Validate executor is known const knownExecutors = new Set( Object.values(EXECUTOR_ASSIGNMENT_TABLE).map((c) => c.executor), ); if (story.executor && !knownExecutors.has(story.executor)) { errors.push(`Unknown executor: ${story.executor}. Known executors: ${[...knownExecutors].join(', ')}`); } // Validate quality gate is known const knownQualityGates = new Set( Object.values(EXECUTOR_ASSIGNMENT_TABLE).map((c) => c.quality_gate), ); // Add @pm as it can be a quality gate knownQualityGates.add('@pm'); if (story.quality_gate && !knownQualityGates.has(story.quality_gate)) { errors.push( `Unknown quality_gate: ${story.quality_gate}. Known quality gates: ${[...knownQualityGates].join(', ')}`, ); } return { isValid: errors.length === 0, errors, }; } /** * Gets all known story types * * @returns {string[]} Array of story type keys */ function getStoryTypes() { return Object.keys(EXECUTOR_ASSIGNMENT_TABLE); } /** * Gets the configuration for a specific story type * * @param {string} storyType - The story type key * @returns {StoryTypeConfig|null} The configuration or null if not found */ function getStoryTypeConfig(storyType) { return EXECUTOR_ASSIGNMENT_TABLE[storyType] || null; } /** * Gets all executors and their assigned work types * * @returns {Object.<string, string[]>} Map of executor to array of work types */ function getExecutorWorkTypes() { const executorMap = {}; for (const [typeKey, config] of Object.entries(EXECUTOR_ASSIGNMENT_TABLE)) { if (!executorMap[config.executor]) { executorMap[config.executor] = []; } executorMap[config.executor].push(typeKey); } return executorMap; } module.exports = { // Main functions detectStoryType, assignExecutor, assignExecutorFromContent, validateExecutorAssignment, // Utility functions getStoryTypes, getStoryTypeConfig, getExecutorWorkTypes, // Constants (exported for external validation/testing) EXECUTOR_ASSIGNMENT_TABLE, DEFAULT_ASSIGNMENT, };