Skip to main content
Glama

π“‚€π“’π“‹Ήπ”Έβ„•π•Œπ”Ήπ•€π•Šπ“‹Ήπ“’π“‚€ - Intelligent Guidance for

by Hive-Academy
role-transition.service.tsβ€’14.4 kB
import { Inject, Injectable, Logger } from '@nestjs/common'; import { ICodeReviewRepository } from '../../task-management/repositories/interfaces/code-review.repository.interface'; import { ITaskRepository } from '../../task-management/repositories/interfaces/task.repository.interface'; import { IStepProgressRepository } from '../repositories/interfaces/step-progress.repository.interface'; import { IWorkflowExecutionRepository } from '../repositories/interfaces/workflow-execution.repository.interface'; import { IWorkflowRoleRepository } from '../repositories/interfaces/workflow-role.repository.interface'; import { WorkflowExecutionService } from './workflow-execution.service'; // Configuration interfaces to eliminate hardcoding export interface QualityGateConfig { testCoverageThreshold: number; documentationMinLength: number; timeoutMs: number; commands: { lint: string; test: string; testUnit: string; testIntegration: string; testE2e: string; testCoverage: string; build: string; }; } export interface TransitionScoringConfig { baseScore: number; commonTransitionBonus: number; randomVariance: number; commonTransitions: string[]; } export interface TransitionValidationResult { valid: boolean; errors: string[]; warnings?: string[]; } export interface AvailableTransition { id: string; transitionName: string; fromRole: { name: string; description: string; }; toRole: { name: string; description: string; }; conditions: any; requirements: any; handoffGuidance?: any; } @Injectable() export class RoleTransitionService { private readonly logger = new Logger(RoleTransitionService.name); // Configuration with sensible defaults private readonly qualityGateConfig: QualityGateConfig = { testCoverageThreshold: 80, documentationMinLength: 100, timeoutMs: 30000, commands: { lint: 'npm run lint', test: 'npm test', testUnit: 'npm run test:unit', testIntegration: 'npm run test:integration', testE2e: 'npm run test:e2e', testCoverage: 'npm run test:coverage', build: 'npm run build', }, }; private readonly scoringConfig: TransitionScoringConfig = { baseScore: 50, commonTransitionBonus: 20, randomVariance: 30, commonTransitions: [ 'product_manager_to_architect', 'architect_to_senior_developer', 'senior_developer_to_code_review', 'code_review_to_product_manager', ], }; constructor( @Inject('IWorkflowRoleRepository') private readonly workflowRoleRepository: IWorkflowRoleRepository, @Inject('IWorkflowExecutionRepository') private readonly workflowExecutionRepository: IWorkflowExecutionRepository, @Inject('IStepProgressRepository') private readonly stepProgressRepository: IStepProgressRepository, @Inject('ITaskRepository') private readonly taskRepository: ITaskRepository, @Inject('ICodeReviewRepository') private readonly codeReviewRepository: ICodeReviewRepository, private workflowExecutionService: WorkflowExecutionService, ) {} /** * Get available role transitions for a given role */ async getAvailableTransitions( fromRoleName: string, ): Promise<AvailableTransition[]> { const fromRole = await this.workflowRoleRepository.findByName(fromRoleName); if (!fromRole) { throw new Error(`Role '${fromRoleName}' not found`); } // Get transitions from the database using the repository method const transitions = await this.workflowRoleRepository.findTransitionsFromRole(fromRole.id); return transitions.map((transition: any) => ({ id: transition.id, transitionName: transition.transitionName, fromRole: { name: fromRole.name, description: fromRole.description, }, toRole: { name: transition.toRole.name, description: transition.toRole.description, }, conditions: transition.conditions, // Populated from structured conditions requirements: transition.requirements, // Populated from structured requirements handoffGuidance: { contextElements: transition.contextElements, deliverables: transition.deliverables, handoffMessage: transition.handoffMessage, }, })); } /** * Validate if a role transition can be performed */ async validateTransition( transitionId: string, context: { roleId: string; taskId: string; projectPath?: string }, ): Promise<TransitionValidationResult> { try { const transition = await this.workflowRoleRepository.findTransitionById(transitionId); if (!transition) { return { valid: false, errors: ['Transition not found'] }; } const errors: string[] = []; const warnings: string[] = []; // Validate transition conditions using structured data if ( transition.conditions && (transition.conditions as { name: string; value: boolean }[]).length > 0 ) { const conditionResult = await this.validateStructuredTransitionConditions( transition.conditions as { name: string; value: boolean }[], context, ); if (!conditionResult.valid) { errors.push(...conditionResult.errors); } if (conditionResult.warnings) { warnings.push(...conditionResult.warnings); } } return { valid: errors.length === 0, errors, warnings: warnings.length > 0 ? warnings : undefined, }; } catch (error) { return { valid: false, errors: [`Validation error: ${error.message}`], }; } } /** * Execute a role transition */ async executeTransition( transitionId: string, context: { roleId: string; taskId: string; projectPath?: string }, handoffMessage?: string, ): Promise<{ success: boolean; message: string; newRoleId?: string }> { try { // First validate the transition const validation = await this.validateTransition(transitionId, context); if (!validation.valid) { return { success: false, message: `Transition validation failed: ${validation.errors.join(', ')}`, }; } const transition = await this.workflowRoleRepository.findTransitionById(transitionId); if (!transition) { return { success: false, message: 'Transition not found' }; } // Record the transition in the task workflow await this.recordTransition(transition, context, handoffMessage); // Update task ownership if needed await this.updateTaskOwnership( String(context.taskId), transition.toRole.name, ); // πŸ”§ FIX: Update workflow execution state after role transition await this.updateWorkflowExecutionStateForTransition( context.taskId, transition.toRole.id, handoffMessage, ); return { success: true, message: `Successfully transitioned from ${transition.fromRole.description} to ${transition.toRole.description}`, newRoleId: transition.toRole.id, }; } catch (error) { return { success: false, message: `Transition execution failed: ${error.message}`, }; } } /** * Get transition history for a task */ getTransitionHistory(taskId: number) { return this.workflowRoleRepository.findDelegationHistory(taskId.toString()); } /** * Get recommended next transitions based on current context */ async getRecommendedTransitions( currentRoleName: string, context: { roleId: string; taskId: string }, ): Promise<AvailableTransition[]> { const availableTransitions = await this.getAvailableTransitions(currentRoleName); // Filter and rank transitions based on context const recommendedTransitions = []; for (const transition of availableTransitions) { const validation = await this.validateTransition(transition.id, context); // Only recommend transitions that are valid or have only warnings if ( validation.valid || (validation.errors.length === 0 && validation.warnings) ) { recommendedTransitions.push({ ...transition, recommendationScore: this.calculateRecommendationScore(transition), }); } } // Sort by recommendation score (highest first) return recommendedTransitions .sort( (a, b) => (b as any).recommendationScore - (a as any).recommendationScore, ) .slice(0, 3); // Return top 3 recommendations } // Private helper methods private async validateStructuredTransitionConditions( conditions: Array<{ name: string; value: boolean }>, context: { roleId: string; taskId: string; projectPath?: string }, ): Promise<{ valid: boolean; errors: string[]; warnings?: string[] }> { const errors: string[] = []; const warnings: string[] = []; for (const condition of conditions) { // Validate condition based on its name and required value const isConditionMet = await this.checkCondition(condition.name, context); if (condition.value && !isConditionMet) { errors.push(`Required condition '${condition.name}' not met`); } else if (!condition.value && isConditionMet) { warnings.push(`Condition '${condition.name}' is met but not required`); } } return { valid: errors.length === 0, errors, warnings }; } private async checkCondition( conditionName: string, context: { roleId: string; taskId: string; projectPath?: string }, ): Promise<boolean> { switch (conditionName) { case 'allStepsCompleted': return this.checkAllStepsCompleted(context); case 'taskStatusReady': return this.checkTaskStatus(context, 'READY'); case 'reviewCompleted': return this.checkReviewCompleted(context); default: return true; // Unknown conditions pass by default } } private async checkAllStepsCompleted(context: { roleId: string; taskId: string; }): Promise<boolean> { const incompleteSteps = await this.stepProgressRepository.findIncompleteForRole(context.roleId); return !incompleteSteps; } private async checkTaskStatus( context: { taskId: string }, requiredStatus: string, ): Promise<boolean> { const task = await this.taskRepository.findById(Number(context.taskId)); return task?.status === requiredStatus; } private async checkReviewCompleted(context: { taskId: string; }): Promise<boolean> { const review = await this.codeReviewRepository.findLatestByTaskId( Number(context.taskId), ); return !!review; } private calculateRecommendationScore( transition: AvailableTransition, ): number { // This would implement sophisticated recommendation logic // For now, we'll use a simple scoring system let score = this.scoringConfig.baseScore; // Boost score for common transitions if ( this.scoringConfig.commonTransitions.includes(transition.transitionName) ) { score += this.scoringConfig.commonTransitionBonus; } // Add randomness to simulate context-based scoring score += Math.random() * this.scoringConfig.randomVariance; return score; } /** * Update quality gate configuration */ updateQualityGateConfig(config: Partial<QualityGateConfig>): void { Object.assign(this.qualityGateConfig, config); } /** * Update transition scoring configuration */ updateScoringConfig(config: Partial<TransitionScoringConfig>): void { Object.assign(this.scoringConfig, config); } /** * Get current configuration */ getConfiguration(): { qualityGate: QualityGateConfig; scoring: TransitionScoringConfig; } { return { qualityGate: { ...this.qualityGateConfig }, scoring: { ...this.scoringConfig }, }; } /** * Update workflow execution state after role transition */ private async updateWorkflowExecutionStateForTransition( taskId: string, newRoleId: string, handoffMessage?: string, ): Promise<void> { // Get the workflow execution for this task const execution = await this.workflowExecutionRepository.findByTaskId( Number(taskId), ); if (!execution) { return; } // Update core fields first (simplified: no step lookup) await this.workflowExecutionRepository.update(execution.id, { currentRoleId: newRoleId, currentStepId: undefined, // Simplified: no step assignment }); // Update state with simplified execution state await this.workflowExecutionService.updateExecutionState(execution.id, { phase: 'role_transitioned', lastTransition: { timestamp: new Date().toISOString(), newRoleId, handoffMessage: handoffMessage || 'Role transition executed', }, }); } /** * Record a role transition in the task workflow */ private async recordTransition( transition: any, context: { roleId: string; taskId: string; projectPath?: string }, handoffMessage?: string, ): Promise<void> { try { // Record transition in workflow execution state await this.updateWorkflowExecutionStateForTransition( context.taskId, transition.toRole.id, handoffMessage, ); this.logger.log( `Recorded transition ${transition.id} for task ${context.taskId}`, ); } catch (error) { this.logger.error('Failed to record transition:', error); throw error; } } /** * Update task ownership after role transition */ private async updateTaskOwnership( taskId: string, newRoleName: string, ): Promise<void> { try { // Update task ownership - for now just log since we may not have ownership tracking this.logger.log( `Updated task ${taskId} ownership to role ${newRoleName}`, ); // If we had task ownership tracking, we would update it here: await this.taskRepository.update(Number(taskId), { owner: newRoleName, currentMode: newRoleName, }); return Promise.resolve(); } catch (error) { this.logger.error('Failed to update task ownership:', error); throw error; } } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Hive-Academy/Anubis-MCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server