// @lifecycle canonical - Loads shell verification presets from YAML configuration.
/**
* Shell Preset Loader
*
* Loads shell verification presets from YAML configuration,
* enabling runtime customization without code changes.
*
* @example
* ```typescript
* const presets = loadShellPresets();
* const fastConfig = presets.fast;
* // { maxIterations: 1, timeout: 30000 }
* ```
*/
import { existsSync } from 'fs';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
import { loadYamlFileSync } from '../../../shared/utils/yaml/index.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
/**
* Shell verification preset configuration.
*/
export interface ShellPresetConfig {
readonly maxIterations: number;
readonly timeout: number;
readonly description?: string;
}
/**
* All available shell presets.
*/
export interface ShellPresets {
readonly fast: ShellPresetConfig;
readonly full: ShellPresetConfig;
readonly extended: ShellPresetConfig;
[key: string]: ShellPresetConfig | undefined;
}
/**
* Shell verification defaults.
*/
export interface ShellDefaults {
readonly maxAttempts: number;
readonly defaultTimeout: number;
readonly maxTimeout: number;
}
/**
* Full YAML configuration structure.
*/
interface ShellPresetsYaml {
presets: Record<string, ShellPresetConfig>;
defaults?: Partial<ShellDefaults>;
}
// Default presets as fallback if YAML loading fails
const DEFAULT_PRESETS: ShellPresets = {
fast: {
maxIterations: 1,
timeout: 30000, // 30 seconds
},
full: {
maxIterations: 5,
timeout: 300000, // 5 minutes
},
extended: {
maxIterations: 10,
timeout: 600000, // 10 minutes
},
};
const DEFAULT_SHELL_DEFAULTS: ShellDefaults = {
maxAttempts: 5,
defaultTimeout: 300000,
maxTimeout: 600000,
};
// Cached values (loaded once)
let cachedPresets: ShellPresets | null = null;
let cachedDefaults: ShellDefaults | null = null;
/**
* Resolve the path to the shell presets config file.
*/
function resolveConfigPath(): string | null {
const candidates = [
// From src/gates/config/ -> resources/gates/config/
join(__dirname, '..', '..', '..', 'resources', 'gates', 'config', 'shell-presets.yaml'),
// From dist/gates/config/ -> resources/gates/config/
join(__dirname, '..', '..', '..', '..', 'resources', 'gates', 'config', 'shell-presets.yaml'),
// Environment variable override
process.env['MCP_SHELL_PRESETS_PATH'],
].filter(Boolean) as string[];
for (const path of candidates) {
if (existsSync(path)) {
return path;
}
}
return null;
}
/**
* Load shell presets from YAML configuration.
* Falls back to default presets if loading fails.
*
* @param forceReload - If true, bypass cache and reload from file
* @returns Shell presets object
*/
export function loadShellPresets(forceReload = false): ShellPresets {
if (cachedPresets && !forceReload) {
return cachedPresets;
}
const configPath = resolveConfigPath();
if (!configPath) {
console.warn('[ShellPresetLoader] Config not found, using defaults');
cachedPresets = DEFAULT_PRESETS;
cachedDefaults = DEFAULT_SHELL_DEFAULTS;
return cachedPresets;
}
try {
const config = loadYamlFileSync<ShellPresetsYaml>(configPath);
if (!config?.presets) {
console.warn('[ShellPresetLoader] Invalid config structure, using defaults');
cachedPresets = DEFAULT_PRESETS;
cachedDefaults = DEFAULT_SHELL_DEFAULTS;
return cachedPresets;
}
// Merge loaded presets with defaults to ensure required presets exist
cachedPresets = {
fast: config.presets['fast'] ?? DEFAULT_PRESETS.fast,
full: config.presets['full'] ?? DEFAULT_PRESETS.full,
extended: config.presets['extended'] ?? DEFAULT_PRESETS.extended,
...config.presets,
};
// Load defaults
if (config.defaults) {
cachedDefaults = {
maxAttempts: config.defaults.maxAttempts ?? DEFAULT_SHELL_DEFAULTS.maxAttempts,
defaultTimeout: config.defaults.defaultTimeout ?? DEFAULT_SHELL_DEFAULTS.defaultTimeout,
maxTimeout: config.defaults.maxTimeout ?? DEFAULT_SHELL_DEFAULTS.maxTimeout,
};
} else {
cachedDefaults = DEFAULT_SHELL_DEFAULTS;
}
return cachedPresets;
} catch (error) {
console.error('[ShellPresetLoader] Failed to load config:', error);
cachedPresets = DEFAULT_PRESETS;
cachedDefaults = DEFAULT_SHELL_DEFAULTS;
return cachedPresets;
}
}
/**
* Get shell verification defaults.
*/
export function getShellDefaults(): ShellDefaults {
// Ensure presets are loaded (which also loads defaults)
loadShellPresets();
return cachedDefaults ?? DEFAULT_SHELL_DEFAULTS;
}
/**
* Get a specific preset by name.
* Returns undefined if preset doesn't exist.
*/
export function getShellPreset(name: string): ShellPresetConfig | undefined {
const presets = loadShellPresets();
return presets[name];
}
/**
* Check if a preset name is valid.
*/
export function isValidPresetName(name: string): name is 'fast' | 'full' | 'extended' {
const presets = loadShellPresets();
return name in presets;
}
/**
* Clear cached presets (useful for testing or hot reload).
*/
export function clearShellPresetCache(): void {
cachedPresets = null;
cachedDefaults = null;
}
/**
* Get all preset names.
*/
export function getPresetNames(): string[] {
return Object.keys(loadShellPresets());
}