mcp-integration-server-solin
Version:
MCP Server for Cody, YouTrack, and Forgejo integration
295 lines • 11.3 kB
JavaScript
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