Skip to main content
Glama

CTS MCP Server

by EricA1019
config_loader.js10.6 kB
/** * Configuration Loader for CTS MCP * * Loads .ctsrc.json configuration files with cascading resolution: * 1. Project-level: <projectPath>/.ctsrc.json * 2. User-level: ~/.ctsrc.json * 3. Defaults: hardcoded in schemas * * Features: * - File-based configuration (JSON) * - Hot-reload with file watching * - Cascading config resolution * - CTS rule customization (file size, hop size, etc.) * - Team-specific standards * * Following Quinn's testing methodology: * - Comprehensive validation with Zod * - Type-safe configuration * - Error handling for missing/invalid files * - Observable config changes */ import { z } from 'zod'; import { readFileSync, existsSync, watchFile, unwatchFile } from 'fs'; import { join } from 'path'; import { homedir } from 'os'; import { ToolConfigSchema, ConfigManager } from './tool_config.js'; /** * CTS Rule Thresholds * Customizable limits for CTS compliance rules */ export const CTSRuleThresholdsSchema = z.object({ // File size limits maxFileLines: z.number().min(100).max(2000).default(500), maxFunctionLines: z.number().min(10).max(200).default(50), maxClassLines: z.number().min(50).max(1000).default(300), // Hop size limits maxHopLOC: z.number().min(100).max(5000).default(1500), maxSubHops: z.number().min(1).max(10).default(5), // Complexity thresholds maxCyclomaticComplexity: z.number().min(5).max(50).default(10), maxNestingDepth: z.number().min(2).max(10).default(4), // Signal architecture minSignalUsage: z.number().min(0).max(100).default(70), // % of events that use signals maxDirectCalls: z.number().min(0).max(100).default(30), // % of direct function calls allowed // Code quality requireTypeHints: z.boolean().default(true), requireDocstrings: z.boolean().default(false), minTestCoverage: z.number().min(0).max(100).default(0), // % coverage }); /** * Project-level .ctsrc.json schema * * Example: * ```json * { * "rules": { * "maxFileLines": 750, * "requireTypeHints": false * }, * "exclusions": ["..tests/..", "..examples/.."], * "tools": { * "audit": { * "minScore": 75 * } * } * } * ``` */ export const ProjectConfigSchema = z.object({ // CTS rule customization rules: CTSRuleThresholdsSchema.partial().optional(), // File/directory exclusions (glob patterns) exclusions: z.array(z.string()).optional(), // Tool-specific configuration tools: ToolConfigSchema.optional(), // Team metadata team: z.object({ name: z.string().optional(), standards: z.string().optional(), // Link to team standards doc }).optional(), // Version (for future compatibility) version: z.string().default('1.0'), }); /** * Configuration Loader * * Manages cascading configuration loading and hot-reload */ export class ConfigLoader { mergedConfig; configManager; projectConfigPath; userConfigPath; watchers = new Set(); changeHandlers = new Set(); constructor() { this.userConfigPath = join(homedir(), '.ctsrc.json'); this.mergedConfig = this.getDefaultConfig(); this.configManager = new ConfigManager(); } /** * Load configuration for a specific project * * Cascading resolution: * 1. Load user config (~/.ctsrc.json) * 2. Load project config (<projectPath>/.ctsrc.json) * 3. Merge with defaults * * @param projectPath - Absolute path to project directory * @returns Merged configuration */ loadConfig(projectPath) { // Start with defaults let config = this.getDefaultConfig(); const sources = { defaults: true }; // Layer 1: User-level config const userConfig = this.loadConfigFile(this.userConfigPath); if (userConfig) { config = this.mergeConfigs(config, userConfig); sources.user = this.userConfigPath; } // Layer 2: Project-level config this.projectConfigPath = join(projectPath, '.ctsrc.json'); const projectConfig = this.loadConfigFile(this.projectConfigPath); if (projectConfig) { config = this.mergeConfigs(config, projectConfig); sources.project = this.projectConfigPath; } config.sources = sources; this.mergedConfig = config; // Update tool config manager this.configManager.updateConfig(config.tools); return config; } /** * Enable hot-reload for configuration files * * Watches project and user config files for changes * and automatically reloads configuration * * @param projectPath - Project directory to watch */ enableHotReload(projectPath) { this.stopWatching(); const filesToWatch = [ this.userConfigPath, join(projectPath, '.ctsrc.json'), ].filter(existsSync); for (const file of filesToWatch) { watchFile(file, { interval: 1000 }, (curr, prev) => { if (curr.mtime > prev.mtime) { console.log(`[ConfigLoader] Config file changed: ${file}`); const newConfig = this.loadConfig(projectPath); this.notifyConfigChange(newConfig); } }); this.watchers.add(file); } console.log(`[ConfigLoader] Hot-reload enabled (watching ${this.watchers.size} files)`); } /** * Stop watching configuration files */ stopWatching() { for (const file of this.watchers) { unwatchFile(file); } this.watchers.clear(); } /** * Register a handler for configuration changes * * @param handler - Callback function called when config changes */ onConfigChange(handler) { this.changeHandlers.add(handler); } /** * Unregister a configuration change handler */ offConfigChange(handler) { this.changeHandlers.delete(handler); } /** * Get current merged configuration */ getConfig() { return this.mergedConfig; } /** * Get tool configuration manager */ getToolConfigManager() { return this.configManager; } /** * Load configuration from a file * * @param filePath - Path to .ctsrc.json file * @returns Parsed config or null if file doesn't exist/invalid */ loadConfigFile(filePath) { if (!existsSync(filePath)) { return null; } try { const content = readFileSync(filePath, 'utf-8'); const json = JSON.parse(content); const validated = ProjectConfigSchema.parse(json); return validated; } catch (error) { console.error(`[ConfigLoader] Failed to load config from ${filePath}:`, error); return null; } } /** * Merge two configurations (right overrides left) */ mergeConfigs(base, override) { return { rules: { ...base.rules, ...(override.rules || {}), }, exclusions: override.exclusions || base.exclusions, tools: { ...base.tools, ...(override.tools || {}), }, team: override.team || base.team, sources: base.sources, }; } /** * Get default configuration */ getDefaultConfig() { return { rules: CTSRuleThresholdsSchema.parse({}), exclusions: ['**/addons/**', '**/.godot/**', '**/build/**', '**/dist/**'], tools: {}, sources: { defaults: true }, }; } /** * Notify all handlers of configuration change */ notifyConfigChange(newConfig) { for (const handler of this.changeHandlers) { try { handler(newConfig); } catch (error) { console.error('[ConfigLoader] Error in config change handler:', error); } } } /** * Create example .ctsrc.json file content * * @returns JSON string with all configuration options documented */ static generateExampleConfig() { return JSON.stringify({ "$schema": "https://example.com/ctsrc-schema.json", "version": "1.0", "rules": { "maxFileLines": 500, "maxFunctionLines": 50, "maxClassLines": 300, "maxHopLOC": 1500, "maxSubHops": 5, "maxCyclomaticComplexity": 10, "maxNestingDepth": 4, "minSignalUsage": 70, "maxDirectCalls": 30, "requireTypeHints": true, "requireDocstrings": false, "minTestCoverage": 0 }, "exclusions": [ "**/addons/**", "**/.godot/**", "**/tests/**", "**/examples/**" ], "tools": { "bughunter": { "minSeverity": "medium", "maxFiles": 1000, "excludePatterns": ["**/addons/**", "**/.godot/**"], "enableCache": true }, "audit": { "categories": ["cts", "code_quality", "project_structure"], "minScore": 75, "format": "json" }, "signalScan": { "renderMap": true, "includePrivate": false }, "analysis": { "detectUnused": true, "buildHierarchy": true, "minClusterSize": 5 }, "refactoring": { "includeRename": true, "includeMerge": true, "minConfidence": 0.95, "maxSuggestions": 20 }, "reasoning": { "maxIterations": 10, "initialStage": "Problem Definition" } }, "team": { "name": "Your Team Name", "standards": "https://link-to-your-coding-standards.com" } }, null, 2); } } /** * Global config loader instance */ export const globalConfigLoader = new ConfigLoader(); //# sourceMappingURL=config_loader.js.map

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/EricA1019/CTS_MCP'

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