/** * xray Testing Strategy MCP Server * * Knowledge skill for test planning, coverage optimization, * and testing methodology recommendations */ import * as fs from "fs"; import * as path from "path"; import { frameworkLogger } from "../../core/framework-logger.js"; import { XrayKnowledgeSkillBase } from "../shared/knowledge-skill-base.js"; import { detectProjectLanguage, LANGUAGE_CONFIGS, type LanguageConfig, } from "../../utils/language-detector.js"; interface TestAnalysis { coverage: number; testTypes: Record; testFrameworks: string[]; testPatterns: string[]; gaps: string[]; recommendations: string[]; } interface TestStrategy { unitTests: number; integrationTests: number; e2eTests: number; performanceTests: number; securityTests: number; totalEstimated: number; } interface AnalyzeTestCoverageArgs { projectRoot: string; includeBreakdown?: boolean; coverageThreshold?: number; } interface DesignTestStrategyArgs { projectRoot: string; projectType?: string; complexity?: string; timeline?: string; } interface IdentifyTestGapsArgs { projectRoot: string; sourceFiles?: string[]; existingTests?: string[]; } interface OptimizeTestCoverageArgs { projectRoot: string; currentCoverage?: number; targetCoverage?: number; focusAreas?: string[]; } interface GenerateTestFileArgs { sourceFile: string; sourceContent: string; exports: Array<{ name: string; type: string }>; testFilePath: string; directory?: string; } interface TestGap { sourceFile: string; expectedTest: string; type: string; priority: number; complexity: number; } interface OptimizationPhase { name: string; focus: string; targetCoverage: number; estimatedTests: number; } interface CoverageOptimizationPlan { currentCoverage: number; targetCoverage: number; gap: number; phases: OptimizationPhase[]; estimatedEffort: string; priorityAreas: string[]; } interface OptimizationTimeline { estimatedDays: number; phases: Array<{ name: string; estimatedDays: number }>; } interface OptimizationROI { coverageIncrease: string; effortDays: number; bugsPrevented: number; confidenceIncrease: string; } interface Milestone { milestone: string; tests: number; focus: string; } interface ImplementationPlan { phases: Array<{ name: string; focus: string; estimatedTests: number }>; timeline: string; milestones: Milestone[]; } interface BaseTestCounts { unit: number; integration: number; e2e: number; performance: number; security: number; } class XrayTestingStrategyServer extends XrayKnowledgeSkillBase { constructor() { super("testing-strategy", "3.1.0"); this.tools = [ { name: "analyze-test-coverage", description: "Analyze current test coverage and identify gaps", inputSchema: { type: "object", properties: { projectRoot: { type: "string" }, includeBreakdown: { type: "boolean", default: true }, coverageThreshold: { type: "number", default: 80 } }, required: ["projectRoot"] } }, { name: "design-test-strategy", description: "Design comprehensive testing strategy for the project", inputSchema: { type: "object", properties: { projectRoot: { type: "string" }, projectType: { type: "string", enum: ["web", "api", "mobile", "desktop"] }, complexity: { type: "string", enum: ["simple", "medium", "complex"] }, timeline: { type: "string", enum: ["agile", "waterfall", "continuous"] } }, required: ["projectRoot"] } }, { name: "identify-test-gaps", description: "Identify untested code and recommend test cases", inputSchema: { type: "object", properties: { projectRoot: { type: "string" }, sourceFiles: { type: "array", items: { type: "string" } }, existingTests: { type: "array", items: { type: "string" } } }, required: ["projectRoot"] } }, { name: "optimize-test-coverage", description: "Analyze and optimize test coverage patterns", inputSchema: { type: "object", properties: { projectRoot: { type: "string" }, currentCoverage: { type: "number" }, targetCoverage: { type: "number", default: 85 }, focusAreas: { type: "array", items: { type: "string" } } }, required: ["projectRoot"] } }, { name: "generate-test-file", description: "Generate test file for a source file with exports", inputSchema: { type: "object", properties: { sourceFile: { type: "string", description: "Path to source file" }, sourceContent: { type: "string", description: "Source file content" }, exports: { type: "array", items: { type: "object", properties: { name: { type: "string" }, type: { type: "string" } } }, description: "Exported functions/classes to test" }, testFilePath: { type: "string", description: "Path for generated test file" }, directory: { type: "string", description: "Project directory" } }, required: ["sourceFile", "sourceContent", "exports", "testFilePath"] } }, ]; this.handlers = { "analyze-test-coverage": async (args) => this.analyzeTestCoverage(args as AnalyzeTestCoverageArgs), "design-test-strategy": async (args) => this.designTestStrategy(args as DesignTestStrategyArgs), "identify-test-gaps": async (args) => this.identifyTestGaps(args as IdentifyTestGapsArgs), "optimize-test-coverage": async (args) => this.optimizeTestCoverage(args as OptimizeTestCoverageArgs), "generate-test-file": async (args) => this.generateTestFile(args as GenerateTestFileArgs), }; this.setupToolHandlers(); } // setupToolHandlers removed — handled by XrayKnowledgeSkillBase via this.tools + this.handlers private async analyzeTestCoverage(args: AnalyzeTestCoverageArgs) { const { projectRoot, includeBreakdown = true, coverageThreshold = 80, } = args; const analysis = this.performTestCoverageAnalysis(projectRoot); return { content: [ { type: "text", text: JSON.stringify( { projectRoot, analysis, threshold: coverageThreshold, status: analysis.coverage >= coverageThreshold ? "adequate" : "insufficient", analyzedAt: new Date().toISOString(), }, null, 2, ), }, ], }; } private async designTestStrategy(args: DesignTestStrategyArgs) { const { projectRoot, projectType = "web", complexity = "medium", timeline = "agile" } = args; const strategy = this.generateTestStrategy( projectRoot, projectType, complexity, timeline, ); return { content: [ { type: "text", text: JSON.stringify( { projectRoot, strategy, recommendations: this.generateStrategyRecommendations( strategy, projectType, ), implementation: this.createImplementationPlan(strategy, timeline), analyzedAt: new Date().toISOString(), }, null, 2, ), }, ], }; } private async identifyTestGaps(args: IdentifyTestGapsArgs) { const { projectRoot, sourceFiles, existingTests } = args; const gaps = this.analyzeTestGaps(sourceFiles || [], existingTests || []); return { content: [ { type: "text", text: JSON.stringify( { projectRoot, gaps, coverage: this.calculateGapCoverage( gaps, sourceFiles?.length || 0, ), recommendations: this.generateGapRecommendations(gaps), analyzedAt: new Date().toISOString(), }, null, 2, ), }, ], }; } private async optimizeTestCoverage(args: OptimizeTestCoverageArgs) { const { projectRoot, currentCoverage, targetCoverage = 85, focusAreas, } = args; const optimization = this.createCoverageOptimizationPlan( projectRoot, currentCoverage ?? 0, targetCoverage, focusAreas, ); return { content: [ { type: "text", text: JSON.stringify( { projectRoot, optimization, timeline: this.estimateOptimizationTimeline(optimization), roi: this.calculateOptimizationROI(optimization), analyzedAt: new Date().toISOString(), }, null, 2, ), }, ], }; } private async generateTestFile(args: GenerateTestFileArgs) { const { sourceFile, sourceContent, exports, testFilePath, directory } = args; await frameworkLogger.log('testing-strategy.server', 'generate-test-file-called', 'info', { message: `[testing-strategy] generateTestFile called:` }); await frameworkLogger.log('testing-strategy.server', 'source-file', 'info', { sourceFile }); await frameworkLogger.log('testing-strategy.server', 'test-file-path', 'info', { testFilePath }); await frameworkLogger.log('testing-strategy.server', 'directory', 'info', { directory }); await frameworkLogger.log('testing-strategy.server', 'exports', 'info', { exports: JSON.stringify(exports) }); // Use dynamic import for ESM compatibility const fs = await import("fs"); const pathModule = await import("path"); // Resolve paths relative to the project directory const projectDir = directory || process.cwd(); const resolvedTestPath = pathModule.isAbsolute(testFilePath) ? testFilePath : pathModule.join(projectDir, testFilePath); await frameworkLogger.log('testing-strategy.server', 'resolved-test-path', 'info', { resolvedTestPath }); const testDir = pathModule.dirname(resolvedTestPath); // Ensure test directory exists if (!fs.existsSync(testDir)) { fs.mkdirSync(testDir, { recursive: true }); } const relativeSourcePath = pathModule.relative( testDir, sourceFile.replace(/\.ts$/, ""), ); const importPath = relativeSourcePath.startsWith(".") ? relativeSourcePath : `./${relativeSourcePath}`; // Generate test content const imports = exports.map((e: { name: string }) => e.name).join(", "); const testCases = exports .map((exp: { name: string; type: string }) => { if (exp.type === "function") { return ` describe("${exp.name}", () => { it("should be defined", () => { expect(typeof ${exp.name}).toBe("function"); }); it("should handle basic case", async () => { const result = await ${exp.name}(); expect(result).toBeDefined(); }); });`; } else if (exp.type === "class") { return ` describe("${exp.name}", () => { it("should instantiate", () => { const instance = new ${exp.name}(); expect(instance).toBeInstanceOf(${exp.name}); }); });`; } else { return ` describe("${exp.name}", () => { it("should be defined", () => { expect(${exp.name}).toBeDefined(); }); });`; } }) .join("\n"); const testContent = `/** * Auto-generated test file for ${sourceFile} * Generated by xray Testing Strategy Server * @generated */ import { describe, it, expect } from "vitest"; import { ${imports} } from "${importPath}"; describe("${pathModule.basename(sourceFile, ".ts")}", () => {${testCases} }); `; // Write the test file fs.writeFileSync(resolvedTestPath, testContent, "utf8"); await frameworkLogger.log('testing-strategy.server', 'test-file-written', 'info', { resolvedTestPath }); await frameworkLogger.log('testing-strategy.server', 'file-exists', 'info', { resolvedTestPath, exists: fs.existsSync(resolvedTestPath) }); return { content: [ { type: "text", text: JSON.stringify( { success: true, testFile: testFilePath, exportsGenerated: exports.length, message: `Test file generated successfully at ${testFilePath}`, }, null, 2, ), }, ], }; } // Helper methods private performTestCoverageAnalysis(projectRoot: string): TestAnalysis { const testFiles = this.findTestFiles(projectRoot); const sourceFiles = this.findSourceFiles(projectRoot); const analysis: TestAnalysis = { coverage: this.calculateCoverage(testFiles, sourceFiles), testTypes: this.categorizeTests(testFiles), testFrameworks: this.detectTestFrameworks(testFiles), testPatterns: this.analyzeTestPatterns(testFiles), gaps: this.identifyCoverageGaps(sourceFiles, testFiles), recommendations: [], }; analysis.recommendations = this.generateCoverageRecommendations(analysis); return analysis; } private generateTestStrategy( projectRoot: string, projectType: string, complexity: string, timeline: string, ): TestStrategy { const baseTests = this.estimateBaseTestCounts(projectType, complexity); // Adjust for timeline const multiplier = timeline === "agile" ? 1.2 : timeline === "continuous" ? 1.4 : 1.0; return { unitTests: Math.round(baseTests.unit * multiplier), integrationTests: Math.round(baseTests.integration * multiplier), e2eTests: Math.round(baseTests.e2e * multiplier), performanceTests: Math.round(baseTests.performance * multiplier), securityTests: Math.round(baseTests.security * multiplier), totalEstimated: 0, // Will be calculated }; } private analyzeTestGaps( sourceFiles: string[], existingTests: string[], ): TestGap[] { const gaps: TestGap[] = []; for (const sourceFile of sourceFiles) { const expectedTest = this.generateExpectedTestFile(sourceFile); const hasTest = existingTests.some((test) => test.includes(expectedTest)); if (!hasTest) { gaps.push({ sourceFile, expectedTest, type: this.inferTestType(sourceFile), priority: this.calculateTestPriority(sourceFile), complexity: this.estimateTestComplexity(sourceFile), }); } } return gaps.sort((a, b) => b.priority - a.priority); } private createCoverageOptimizationPlan( projectRoot: string, currentCoverage: number, targetCoverage: number, focusAreas?: string[], ): CoverageOptimizationPlan { const gap = targetCoverage - currentCoverage; const plan: CoverageOptimizationPlan = { currentCoverage, targetCoverage, gap, phases: [], estimatedEffort: this.estimateOptimizationEffort(gap), priorityAreas: this.identifyPriorityAreas(projectRoot, focusAreas), }; // Create optimization phases if (gap > 0) { plan.phases = this.createOptimizationPhases(gap, focusAreas); } return plan; } // Implementation helpers private findTestFiles(projectRoot: string): string[] { return this.findFiles( projectRoot, (file) => file.includes(".test.") || file.includes(".spec.") || file.includes("/__tests__/"), ); } private findSourceFiles(projectRoot: string): string[] { // Use language detector to find supported extensions const projectLanguage = detectProjectLanguage(projectRoot); const langConfig = projectLanguage ? LANGUAGE_CONFIGS.find( (c: LanguageConfig) => c.language === projectLanguage.language, ) : null; const supportedExtensions = langConfig?.extensions || [ ".ts", ".js", ".tsx", ".jsx", ".py", ".java", ".go", ".rs", ".cs", ".rb", ".php", ]; return this.findFiles(projectRoot, (file) => { const ext = path.extname(file); return ( supportedExtensions.includes(ext) && !file.includes(".test.") && !file.includes(".spec.") && !file.includes("/__tests__/") ); }); } private findFiles( projectRoot: string, filter: (file: string) => boolean, ): string[] { const files: string[] = []; const traverse = (dir: string) => { try { const entries = fs.readdirSync(dir, { withFileTypes: true }); for (const entry of entries) { const entryPath = path.join(dir, entry.name); if (this.shouldIgnorePath(entryPath)) continue; if (entry.isFile() && filter(entryPath)) { files.push(entryPath); } else if (entry.isDirectory()) { traverse(entryPath); } } } catch (error) { // Skip inaccessible directories } }; traverse(projectRoot); return files; } private calculateCoverage( testFiles: string[], sourceFiles: string[], ): number { if (sourceFiles.length === 0) return 0; return Math.min( 100, Math.round((testFiles.length / sourceFiles.length) * 100), ); } private categorizeTests(testFiles: string[]): Record { const categories: Record = { unit: 0, integration: 0, e2e: 0, performance: 0, security: 0, }; for (const testFile of testFiles) { const content = fs.readFileSync(testFile, "utf8").toLowerCase(); if (content.includes("integration") || content.includes("e2e")) { categories.integration = (categories.integration || 0) + 1; } else if ( content.includes("performance") || content.includes("benchmark") ) { categories.performance = (categories.performance || 0) + 1; } else if (content.includes("security") || content.includes("auth")) { categories.security = (categories.security || 0) + 1; } else { categories.unit = (categories.unit || 0) + 1; } } return categories; } private detectTestFrameworks(testFiles: string[]): string[] { const frameworks: string[] = []; const sampleFiles = testFiles.slice(0, 5); for (const file of sampleFiles) { try { const content = fs.readFileSync(file, "utf8"); if (content.includes("describe(") && content.includes("it(")) { if (content.includes("vitest")) frameworks.push("vitest"); else if (content.includes("jest")) frameworks.push("jest"); else frameworks.push("mocha/chai"); } if (content.includes("pytest")) frameworks.push("pytest"); if (content.includes("junit")) frameworks.push("junit"); } catch (error) { // Skip } } return Array.from(new Set(frameworks)); } private analyzeTestPatterns(testFiles: string[]): string[] { const patterns: string[] = []; const sampleFiles = testFiles.slice(0, 3); for (const file of sampleFiles) { try { const content = fs.readFileSync(file, "utf8"); if (content.includes("beforeEach") || content.includes("afterEach")) { patterns.push("setup/teardown"); } if (content.includes("mock") || content.includes("stub")) { patterns.push("mocking"); } if (content.includes("fixture") || content.includes("factory")) { patterns.push("test data factories"); } } catch (error) { // Skip } } return Array.from(new Set(patterns)); } private identifyCoverageGaps( sourceFiles: string[], testFiles: string[], ): string[] { const gaps: string[] = []; for (const sourceFile of sourceFiles) { const hasTest = testFiles.some((testFile) => testFile .replace(".test.", ".") .replace(".spec.", ".") .replace("/__tests__/", "/") .includes(path.basename(sourceFile, path.extname(sourceFile))), ); if (!hasTest) { gaps.push(sourceFile); } } return gaps; } private generateCoverageRecommendations(analysis: TestAnalysis): string[] { const recommendations: string[] = []; if (analysis.coverage < 70) { recommendations.push("Increase test coverage to at least 80%"); } if ( (analysis.testTypes.unit || 0) < (analysis.testTypes.integration || 0) ) { recommendations.push("Focus more on unit tests before integration tests"); } if (analysis.gaps.length > 5) { recommendations.push( `Address ${analysis.gaps.length} files without test coverage`, ); } return recommendations; } private estimateBaseTestCounts(projectType: string, complexity: string): BaseTestCounts { const baseMultipliers = { web: { unit: 10, integration: 3, e2e: 2, performance: 1, security: 1 }, api: { unit: 8, integration: 4, e2e: 1, performance: 2, security: 2 }, mobile: { unit: 12, integration: 5, e2e: 3, performance: 1, security: 1 }, desktop: { unit: 15, integration: 4, e2e: 2, performance: 1, security: 1, }, }; const complexityMultipliers = { simple: 0.7, medium: 1.0, complex: 1.5, }; const base = baseMultipliers[projectType as keyof typeof baseMultipliers] || baseMultipliers.web; const multiplier = complexityMultipliers[complexity as keyof typeof complexityMultipliers] || 1.0; return { unit: Math.round(base.unit * multiplier), integration: Math.round(base.integration * multiplier), e2e: Math.round(base.e2e * multiplier), performance: Math.round(base.performance * multiplier), security: Math.round(base.security * multiplier), }; } private generateStrategyRecommendations( strategy: TestStrategy, projectType: string, ): string[] { const recommendations: string[] = []; if (strategy.unitTests < strategy.integrationTests) { recommendations.push( "Prioritize unit tests for better test pyramid balance", ); } if (projectType === "api" && strategy.e2eTests < 2) { recommendations.push("Increase E2E tests for API contract validation"); } return recommendations; } private createImplementationPlan( strategy: TestStrategy, timeline: string, ): ImplementationPlan { const phases = timeline === "agile" ? ["sprint1", "sprint2", "sprint3"] : timeline === "continuous" ? ["week1", "week2", "week3", "week4"] : ["phase1", "phase2", "phase3"]; return { phases: phases.map((phase, index) => ({ name: phase, focus: index === 0 ? "unit-tests" : index === 1 ? "integration-tests" : "e2e-tests", estimatedTests: Math.round(strategy.totalEstimated / phases.length), })), timeline, milestones: this.generateMilestones(strategy, timeline), }; } private generateExpectedTestFile(sourceFile: string): string { const ext = path.extname(sourceFile); const baseName = path.basename(sourceFile, ext); const dir = path.dirname(sourceFile); return path.join(dir, "__tests__", `${baseName}.test${ext}`); } private inferTestType(sourceFile: string): string { const content = fs.readFileSync(sourceFile, "utf8"); if (content.includes("export") && content.includes("function")) { return "unit"; } if (content.includes("router") || content.includes("endpoint")) { return "integration"; } if (content.includes("component") || content.includes("render")) { return "component"; } return "unit"; } private calculateTestPriority(sourceFile: string): number { const content = fs.readFileSync(sourceFile, "utf8"); let priority = 1; // Critical business logic gets higher priority if ( content.includes("auth") || content.includes("security") || content.includes("payment") ) { priority += 3; } // Complex functions get higher priority if ((content.match(/function/g) || []).length > 5) { priority += 2; } // Public APIs get higher priority if (content.includes("export")) { priority += 1; } return Math.min(priority, 5); } private estimateTestComplexity(sourceFile: string): number { const content = fs.readFileSync(sourceFile, "utf8"); const functions = (content.match(/function|class|interface/g) || []).length; const branches = (content.match(/if|switch|catch/g) || []).length; const asyncOps = (content.match(/async|await|Promise/g) || []).length; return Math.min(Math.round((functions + branches + asyncOps) / 3), 5); } private calculateGapCoverage(gaps: TestGap[], totalFiles: number): number { if (totalFiles === 0) return 100; return Math.round(((totalFiles - gaps.length) / totalFiles) * 100); } private generateGapRecommendations(gaps: TestGap[]): string[] { const recommendations: string[] = []; const highPriorityGaps = gaps.filter((g) => g.priority >= 4); if (highPriorityGaps.length > 0) { recommendations.push( `Address ${highPriorityGaps.length} high-priority test gaps first`, ); } const complexGaps = gaps.filter((g) => g.complexity >= 4); if (complexGaps.length > 0) { recommendations.push( `Plan additional time for ${complexGaps.length} complex test implementations`, ); } return recommendations; } private estimateOptimizationEffort(gap: number): string { if (gap <= 10) return "1-2 days"; if (gap <= 20) return "3-5 days"; if (gap <= 30) return "1-2 weeks"; return "2-4 weeks"; } private identifyPriorityAreas( projectRoot: string, focusAreas?: string[], ): string[] { const areas = focusAreas || [ "authentication", "data-validation", "api-endpoints", "ui-components", ]; // Analyze codebase to identify actual priority areas const sourceFiles = this.findSourceFiles(projectRoot); const priorityAreas: string[] = []; for (const area of areas) { const relevantFiles = sourceFiles.filter((file) => fs .readFileSync(file, "utf8") .toLowerCase() .includes(area.toLowerCase()), ); if (relevantFiles.length > 0) { priorityAreas.push(`${area} (${relevantFiles.length} files)`); } } return priorityAreas; } private createOptimizationPhases(gap: number, focusAreas?: string[]): OptimizationPhase[] { const phases: OptimizationPhase[] = []; if (gap > 30) { phases.push({ name: "Phase 1: Critical Paths", focus: "authentication, security, payment flows", targetCoverage: 70, estimatedTests: Math.round(gap * 0.4), }); } if (gap > 20) { phases.push({ name: "Phase 2: Core Functionality", focus: "main business logic, data validation", targetCoverage: 80, estimatedTests: Math.round(gap * 0.4), }); } phases.push({ name: "Phase 3: Edge Cases & UI", focus: "error handling, ui components, edge cases", targetCoverage: 85, estimatedTests: Math.round(gap * 0.2), }); return phases; } private estimateOptimizationTimeline(optimization: CoverageOptimizationPlan): OptimizationTimeline { const baseDays = optimization.phases.length * 3; const complexityMultiplier = optimization.gap > 20 ? 1.5 : 1.0; return { estimatedDays: Math.round(baseDays * complexityMultiplier), phases: optimization.phases.map((phase) => ({ name: phase.name, estimatedDays: Math.round(phase.estimatedTests / 5), })), }; } private calculateOptimizationROI(optimization: CoverageOptimizationPlan): OptimizationROI { const effortDays = optimization.estimatedEffort.includes("days") ? parseInt(optimization.estimatedEffort) : 14; const coverageIncrease = optimization.gap; return { coverageIncrease: `${coverageIncrease}%`, effortDays, bugsPrevented: Math.round(coverageIncrease * 0.8), // Rough estimate confidenceIncrease: `${Math.min(coverageIncrease * 2, 90)}%`, }; } private generateMilestones(strategy: TestStrategy, timeline: string): Milestone[] { const totalTests = strategy.unitTests + strategy.integrationTests + strategy.e2eTests + strategy.performanceTests + strategy.securityTests; strategy.totalEstimated = totalTests; if (timeline === "agile") { return [ { milestone: "Sprint 1 Complete", tests: Math.round(totalTests * 0.3), focus: "unit tests", }, { milestone: "Sprint 2 Complete", tests: Math.round(totalTests * 0.6), focus: "integration tests", }, { milestone: "Sprint 3 Complete", tests: totalTests, focus: "e2e and performance tests", }, ]; } return [ { milestone: "Phase 1 Complete", tests: Math.round(totalTests * 0.4), focus: "foundation tests", }, { milestone: "Phase 2 Complete", tests: Math.round(totalTests * 0.75), focus: "feature tests", }, { milestone: "Phase 3 Complete", tests: totalTests, focus: "complete coverage", }, ]; } private shouldIgnorePath(filePath: string): boolean { const ignorePatterns = [ /node_modules/, /\.git/, /dist/, /build/, /\.next/, /\.nuxt/, /\.cache/, /\.temp/, /coverage/, /\.nyc_output/, /logs/, /\.DS_Store/, /Thumbs\.db/, ]; return ignorePatterns.some((pattern) => pattern.test(filePath)); } // run() removed — handled by XrayKnowledgeSkillBase } // Start the server if run directly if (import.meta.url === `file://${fs.realpathSync(process.argv[1]!)}`) { const server = new XrayTestingStrategyServer(); server.run().catch(() => {}); } export default XrayTestingStrategyServer;