UNPKG

mcp-integration-server-solin

Version:

MCP Server for Cody, YouTrack, and Forgejo integration

295 lines 11.3 kB
import { YouTrackService } from './youtrack.js'; import { ForgejoService } from './forgejo.js'; import { CodyService } from './cody.js'; export class WorkflowService { youtrackService; forgejoService; codyService; rules = []; eventHistory = []; constructor() { this.youtrackService = new YouTrackService(); this.forgejoService = new ForgejoService(); this.codyService = new CodyService(); this.loadDefaultRules(); } loadDefaultRules() { // 기본 워크플로우 규칙들 this.rules = [ { id: 'auto-issue-from-commit', name: '커밋 메시지 기반 이슈 자동 생성', trigger: { type: 'git_push' }, conditions: [ { field: 'commit.message', operator: 'contains', value: 'fix:' }, { field: 'commit.message', operator: 'contains', value: 'bug:' } ], actions: [ { type: 'create_issue', params: { project: '${env.YOUTRACK_DEFAULT_PROJECT}', type: 'Bug', priority: 'Major' } } ], enabled: true }, { id: 'code-quality-check', name: '코드 품질 자동 검사', trigger: { type: 'git_push' }, actions: [ { type: 'analyze_code', params: { analysisType: 'quality', createIssueOnProblems: true } } ], enabled: true }, { id: 'pr-merge-close-issue', name: 'PR 머지시 연관 이슈 자동 완료', trigger: { type: 'pr_merged' }, conditions: [ { field: 'pr.body', operator: 'contains', value: 'closes #' } ], actions: [ { type: 'update_issue', params: { state: 'Done' } } ], enabled: true } ]; } async processEvent(event) { this.eventHistory.push(event); for (const rule of this.rules) { if (!rule.enabled) continue; if (this.matchesTrigger(event, rule.trigger) && this.matchesConditions(event, rule.conditions || [])) { console.log(`워크플로우 규칙 실행: ${rule.name}`); await this.executeActions(event, rule.actions); } } } matchesTrigger(event, trigger) { if (event.type !== trigger.type) return false; if (trigger.repository && event.data.repository !== trigger.repository) return false; if (trigger.branch && event.data.branch !== trigger.branch) return false; return true; } matchesConditions(event, conditions) { return conditions.every(condition => { const value = this.getNestedValue(event.data, condition.field); if (value === undefined) return false; switch (condition.operator) { case 'equals': return value === condition.value; case 'contains': return value.toString().includes(condition.value); case 'startsWith': return value.toString().startsWith(condition.value); case 'endsWith': return value.toString().endsWith(condition.value); case 'regex': return new RegExp(condition.value).test(value.toString()); default: return false; } }); } getNestedValue(obj, path) { return path.split('.').reduce((current, key) => current?.[key], obj); } async executeActions(event, actions) { for (const action of actions) { try { await this.executeAction(event, action); } catch (error) { console.error(`액션 실행 실패: ${action.type}`, error); } } } async executeAction(event, action) { const params = this.interpolateParams(action.params, event); switch (action.type) { case 'create_issue': if (params.project) { await this.youtrackService.createIssue({ project: params.project, summary: params.summary || `자동 생성된 이슈: ${event.type}`, description: params.description || `이벤트 데이터: ${JSON.stringify(event.data, null, 2)}`, type: params.type || 'Task', priority: params.priority || 'Normal' }); } break; case 'update_issue': if (params.issueId) { await this.youtrackService.updateIssue({ issueId: params.issueId, ...params }); } break; case 'analyze_code': if (event.data.commits) { for (const commit of event.data.commits) { if (commit.modified || commit.added) { const files = [...(commit.modified || []), ...(commit.added || [])]; for (const file of files) { if (this.isCodeFile(file)) { const analysis = await this.codyService.analyzeCode({ code: `// 파일: ${file}\n// 커밋: ${commit.id}`, language: this.getLanguageFromFile(file), analysisType: params.analysisType || 'quality' }); if (params.createIssueOnProblems && analysis.content?.[0]?.text?.includes('문제')) { await this.youtrackService.createIssue({ project: params.project || process.env.YOUTRACK_DEFAULT_PROJECT || 'DEFAULT', summary: `코드 품질 문제: ${file}`, description: `자동 분석 결과:\n${analysis.content?.[0]?.text}`, type: 'Bug', priority: 'Normal' }); } } } } } } break; case 'add_comment': // YouTrack 댓글 추가 구현 break; case 'notify': console.log(`알림: ${params.message || JSON.stringify(event.data)}`); break; } } interpolateParams(params, event) { const result = { ...params }; for (const [key, value] of Object.entries(result)) { if (typeof value === 'string') { // ${event.data.field} 형식의 템플릿 변수 치환 result[key] = value.replace(/\$\{([^}]+)\}/g, (match, path) => { return this.getNestedValue({ event, env: process.env }, path) || match; }); } } return result; } isCodeFile(filename) { const codeExtensions = ['.js', '.ts', '.py', '.java', '.cpp', '.c', '.go', '.rs', '.php', '.rb', '.cs']; return codeExtensions.some(ext => filename.endsWith(ext)); } getLanguageFromFile(filename) { const ext = filename.split('.').pop()?.toLowerCase(); const langMap = { 'js': 'javascript', 'ts': 'typescript', 'py': 'python', 'java': 'java', 'cpp': 'cpp', 'c': 'c', 'go': 'go', 'rs': 'rust', 'php': 'php', 'rb': 'ruby', 'cs': 'csharp' }; return langMap[ext || ''] || 'text'; } // 워크플로우 관리 메서드들 async addRule(rule) { this.rules.push(rule); } async updateRule(ruleId, updates) { const index = this.rules.findIndex(r => r.id === ruleId); if (index !== -1) { this.rules[index] = { ...this.rules[index], ...updates }; } } async enableRule(ruleId) { await this.updateRule(ruleId, { enabled: true }); } async disableRule(ruleId) { await this.updateRule(ruleId, { enabled: false }); } getRules() { return [...this.rules]; } getEventHistory(limit = 100) { return this.eventHistory.slice(-limit); } // Git 웹훅 이벤트 처리 async handleGitWebhook(payload) { const event = { id: `git_${Date.now()}`, timestamp: new Date(), type: 'git_push', source: 'forgejo', data: { repository: payload.repository?.name, branch: payload.ref?.replace('refs/heads/', ''), commits: payload.commits || [], pusher: payload.pusher } }; await this.processEvent(event); } // YouTrack 웹훅 이벤트 처리 async handleYouTrackWebhook(payload) { const event = { id: `yt_${Date.now()}`, timestamp: new Date(), type: payload.action === 'created' ? 'issue_created' : 'issue_updated', source: 'youtrack', data: { issue: payload.issue, project: payload.issue?.project, user: payload.user } }; await this.processEvent(event); } // 일일 배치 작업 async runDailyBatch() { console.log('일일 배치 작업 시작...'); // 코드 품질 레포트 생성 await this.generateQualityReport(); // 진행 중인 이슈 동기화 await this.syncActiveIssues(); // 팀 활동 요약 await this.generateTeamSummary(); console.log('일일 배치 작업 완료'); } async generateQualityReport() { // 구현 예정: 전체 프로젝트 코드 품질 분석 console.log('코드 품질 레포트 생성 중...'); } async syncActiveIssues() { // 구현 예정: 활성 이슈와 Git 커밋 동기화 console.log('이슈 동기화 중...'); } async generateTeamSummary() { // 구현 예정: 팀 활동 요약 생성 console.log('팀 활동 요약 생성 중...'); } } //# sourceMappingURL=workflow.js.map