prompt-plus-plus-mcp
Version:
Advanced MCP server with 44+ metaprompt strategies including AI Core Principles, Vibe Coding Rules, and metadata-driven intelligent selection
434 lines • 17.3 kB
JavaScript
import { readFileSync, readdirSync, statSync, watch, existsSync } from 'fs';
import { join, resolve } from 'path';
import { homedir } from 'os';
import { Cache } from './cache.js';
import { logger } from './logger.js';
import { CollectionsManager } from './collections-manager.js';
export class StrategyManager {
strategies = new Map();
strategiesDir;
customPromptsDir;
categoryMetadata = new Map();
cache = new Cache(600000); // 10 minutes
fileWatcher;
customFileWatcher;
collectionsManager;
constructor(strategiesDir, customPromptsDir) {
try {
if (!strategiesDir) {
strategiesDir = this.findStrategiesDirectory();
}
this.strategiesDir = strategiesDir;
this.customPromptsDir = customPromptsDir || this.findCustomPromptsDirectory();
logger.info(`Loading built-in strategies from: ${this.strategiesDir}`);
if (this.customPromptsDir && existsSync(this.customPromptsDir)) {
logger.info(`Loading custom strategies from: ${this.customPromptsDir}`);
}
// Initialize collections manager
this.collectionsManager = new CollectionsManager();
this.loadStrategies();
this.setupFileWatcher();
const customCount = Array.from(this.strategies.values()).filter(s => s.source === 'custom').length;
const builtInCount = this.strategies.size - customCount;
logger.info(`Loaded ${this.strategies.size} strategies (${builtInCount} built-in, ${customCount} custom) across ${this.categoryMetadata.size} categories`);
}
catch (error) {
logger.error('StrategyManager initialization failed', { error: error instanceof Error ? error.message : String(error) });
throw new Error(`Failed to initialize StrategyManager: ${error instanceof Error ? error.message : String(error)}`);
}
}
findStrategiesDirectory() {
try {
// Find project root by looking for package.json
let currentPath = resolve(import.meta.url.replace('file://', ''));
while (currentPath !== '/') {
const parentPath = resolve(currentPath, '..');
try {
if (readdirSync(parentPath).includes('package.json')) {
return join(parentPath, 'metaprompts');
}
}
catch (err) {
logger.debug(`Could not read directory ${parentPath}`, { error: err });
}
currentPath = parentPath;
}
return 'metaprompts';
}
catch (error) {
logger.warn('Could not find project root, using default metaprompts directory');
return 'metaprompts';
}
}
findCustomPromptsDirectory() {
// Check environment variable first
const envPath = process.env.PROMPT_PLUS_CUSTOM_DIR;
if (envPath && existsSync(envPath)) {
return envPath;
}
// Check common locations
const possiblePaths = [
join(homedir(), '.prompt-plus-plus', 'custom-prompts'),
join(homedir(), '.config', 'prompt-plus-plus', 'custom-prompts'),
join(process.cwd(), 'custom-prompts'),
'./custom-prompts'
];
for (const path of possiblePaths) {
if (existsSync(path)) {
logger.info(`Found custom prompts directory at: ${path}`);
return path;
}
}
logger.debug('No custom prompts directory found');
return undefined;
}
setupFileWatcher() {
try {
this.fileWatcher = watch(this.strategiesDir, { recursive: true }, (eventType, filename) => {
if (filename && filename.endsWith('.json')) {
logger.info(`Built-in strategy file changed: ${filename}, reloading...`);
this.cache.clear();
this.loadStrategies();
}
});
logger.debug('Built-in file watcher setup complete');
}
catch (error) {
logger.warn('Could not setup built-in file watcher', { error: error instanceof Error ? error.message : String(error) });
}
// Setup custom prompts watcher if directory exists
if (this.customPromptsDir && existsSync(this.customPromptsDir)) {
try {
this.customFileWatcher = watch(this.customPromptsDir, { recursive: true }, (eventType, filename) => {
if (filename && filename.endsWith('.json')) {
logger.info(`Custom strategy file changed: ${filename}, reloading...`);
this.cache.clear();
this.loadStrategies();
}
});
logger.debug('Custom file watcher setup complete');
}
catch (error) {
logger.warn('Could not setup custom file watcher', { error: error instanceof Error ? error.message : String(error) });
}
}
}
loadStrategies() {
try {
// Clear existing data
this.strategies.clear();
this.categoryMetadata.clear();
// Load built-in strategies
this.loadStrategiesFromDirectory(this.strategiesDir, 'built-in');
// Load custom strategies if directory exists
if (this.customPromptsDir && existsSync(this.customPromptsDir)) {
this.loadStrategiesFromDirectory(this.customPromptsDir, 'custom');
}
if (this.strategies.size === 0) {
logger.warn('No strategies were loaded');
}
}
catch (error) {
const errorMessage = `Failed to load strategies: ${error instanceof Error ? error.message : String(error)}`;
logger.error(errorMessage);
throw new Error(errorMessage);
}
}
loadStrategiesFromDirectory(dirPath, source = 'built-in') {
try {
const files = readdirSync(dirPath);
for (const file of files) {
const filePath = join(dirPath, file);
try {
const stat = statSync(filePath);
if (stat.isDirectory()) {
// Recursively load from subdirectories
this.loadStrategiesFromDirectory(filePath, source);
}
else if (file.endsWith('.json')) {
this.loadJsonFile(filePath, file, dirPath, source);
}
}
catch (error) {
logger.warn(`Could not process file ${filePath}`, { error: error instanceof Error ? error.message : String(error) });
}
}
}
catch (error) {
const errorMessage = `Failed to read directory ${dirPath}: ${error instanceof Error ? error.message : String(error)}`;
logger.error(errorMessage);
throw new Error(errorMessage);
}
}
loadJsonFile(filePath, file, dirPath, source = 'built-in') {
try {
// Check cache first
const cacheKey = `file:${filePath}`;
let data = this.cache.get(cacheKey);
if (!data) {
const content = readFileSync(filePath, 'utf-8');
if (!content.trim()) {
logger.warn(`Empty JSON file: ${filePath}`);
return;
}
try {
data = JSON.parse(content);
this.cache.set(cacheKey, data);
}
catch (parseError) {
logger.error(`Invalid JSON in file ${filePath}`, { error: parseError instanceof Error ? parseError.message : String(parseError) });
return;
}
}
if (file === '_metadata.json') {
this.loadCategoryMetadata(data, dirPath, source);
}
else {
this.loadStrategy(data, file, dirPath, source);
}
}
catch (error) {
logger.error(`Failed to load JSON file ${filePath}`, { error: error instanceof Error ? error.message : String(error) });
}
}
loadCategoryMetadata(data, dirPath, source = 'built-in') {
try {
const categoryName = dirPath.split('/').pop() || 'unknown';
// Validate required fields
if (!data.category || !data.description) {
logger.warn(`Invalid category metadata in ${dirPath}: missing required fields`);
return;
}
this.categoryMetadata.set(categoryName, data);
logger.debug(`Loaded category metadata: ${categoryName}`);
}
catch (error) {
logger.error(`Failed to load category metadata from ${dirPath}`, { error: error instanceof Error ? error.message : String(error) });
}
}
loadStrategy(data, file, dirPath, source = 'built-in') {
try {
const key = file.replace('.json', '');
// Extract category name from directory path
const pathParts = dirPath.split('/');
const categoryName = pathParts[pathParts.length - 1] || 'unknown';
// For custom strategies, prefix with category to avoid conflicts
const strategyKey = source === 'custom' ? `custom_${categoryName}_${key}` : key;
// Validate required fields
if (!data.name || !data.template) {
logger.warn(`Invalid strategy ${strategyKey}: missing required fields (name, template)`);
return;
}
const strategy = {
key: strategyKey,
name: data.name,
description: data.description || '',
examples: Array.isArray(data.examples) ? data.examples : [],
template: data.template,
category: categoryName, // Always set category from directory name
complexity: data.complexity,
timeInvestment: data.time_investment,
triggers: Array.isArray(data.triggers) ? data.triggers : undefined,
bestFor: Array.isArray(data.best_for) ? data.best_for : undefined,
source,
customCategory: source === 'custom' ? categoryName : undefined
};
this.strategies.set(strategyKey, strategy);
logger.debug(`Loaded ${source} strategy: ${strategyKey}`);
}
catch (error) {
logger.error(`Failed to load strategy from ${file}`, { error: error instanceof Error ? error.message : String(error) });
}
}
getStrategy(key) {
const strategy = this.strategies.get(key);
if (!strategy) {
logger.debug(`Strategy not found: ${key}`);
}
return strategy;
}
getAllStrategies() {
return new Map(this.strategies);
}
getStrategyNames() {
return Array.from(this.strategies.keys());
}
listStrategies() {
const result = {};
for (const [key, strategy] of this.strategies) {
result[key] = {
name: strategy.name,
description: strategy.description
};
}
return result;
}
getStrategyExamples() {
const examples = [];
for (const [key, strategy] of this.strategies) {
for (const example of strategy.examples) {
examples.push([example, key]);
}
}
return examples;
}
getCategoryMetadata() {
return new Map(this.categoryMetadata);
}
getAllCategoriesMetadata() {
const result = {};
for (const [categoryName, metadata] of this.categoryMetadata) {
result[categoryName] = metadata;
}
return result;
}
getCategoryStrategies(categoryName) {
const result = {};
const categoryPath = join(this.strategiesDir, categoryName);
for (const [key, strategy] of this.strategies) {
// Check if strategy belongs to this category by checking its file path
const strategyFiles = this.getStrategyFilesInCategory(categoryPath);
if (strategyFiles.includes(key)) {
result[key] = strategy;
}
}
return result;
}
getStrategyFilesInCategory(categoryPath) {
try {
const files = readdirSync(categoryPath);
return files
.filter(file => file.endsWith('.json') && file !== '_metadata.json')
.map(file => file.replace('.json', ''));
}
catch (error) {
logger.error(`Error reading category directory ${categoryPath}`, { error: error instanceof Error ? error.message : String(error) });
return [];
}
}
getCategoryNames() {
return Array.from(this.categoryMetadata.keys());
}
// Custom prompts specific methods
getCustomStrategies() {
const customStrategies = new Map();
for (const [key, strategy] of this.strategies) {
if (strategy.source === 'custom') {
customStrategies.set(key, strategy);
}
}
return customStrategies;
}
getBuiltInStrategies() {
const builtInStrategies = new Map();
for (const [key, strategy] of this.strategies) {
if (strategy.source !== 'custom') {
builtInStrategies.set(key, strategy);
}
}
return builtInStrategies;
}
getStrategiesBySource(source = 'all') {
if (source === 'all') {
return this.getAllStrategies();
}
const filteredStrategies = new Map();
for (const [key, strategy] of this.strategies) {
if (strategy.source === source) {
filteredStrategies.set(key, strategy);
}
}
return filteredStrategies;
}
getCustomCategories() {
const categories = new Set();
for (const strategy of this.strategies.values()) {
if (strategy.source === 'custom' && strategy.customCategory) {
categories.add(strategy.customCategory);
}
}
return Array.from(categories);
}
// Collections management
getCollectionsManager() {
return this.collectionsManager;
}
getCollectionStrategies(collectionKey) {
const collection = this.collectionsManager.getCollection(collectionKey);
if (!collection) {
logger.warn(`Collection '${collectionKey}' not found`);
return new Map();
}
const collectionStrategies = new Map();
for (const strategyKey of collection.strategies) {
const strategy = this.strategies.get(strategyKey);
if (strategy) {
collectionStrategies.set(strategyKey, strategy);
}
else {
logger.warn(`Strategy '${strategyKey}' in collection '${collectionKey}' not found`);
}
}
return collectionStrategies;
}
validateCollectionStrategies(collectionKey) {
const collection = this.collectionsManager.getCollection(collectionKey);
if (!collection) {
return { valid: [], invalid: [] };
}
const valid = [];
const invalid = [];
for (const strategyKey of collection.strategies) {
if (this.strategies.has(strategyKey)) {
valid.push(strategyKey);
}
else {
invalid.push(strategyKey);
}
}
return { valid, invalid };
}
// Cache management
clearCache() {
this.cache.clear();
logger.info('Strategy cache cleared');
}
getCacheStats() {
return {
size: this.cache.size()
};
}
// Cleanup resources
destroy() {
try {
if (this.fileWatcher) {
this.fileWatcher.close();
logger.debug('Built-in file watcher closed');
}
if (this.customFileWatcher) {
this.customFileWatcher.close();
logger.debug('Custom file watcher closed');
}
this.cache.clear();
this.strategies.clear();
this.categoryMetadata.clear();
logger.info('StrategyManager destroyed');
}
catch (error) {
logger.error('Error during StrategyManager cleanup', { error: error instanceof Error ? error.message : String(error) });
}
}
// Health check
isHealthy() {
return this.strategies.size > 0 && this.categoryMetadata.size > 0;
}
getHealthStatus() {
return {
healthy: this.isHealthy(),
strategiesCount: this.strategies.size,
categoriesCount: this.categoryMetadata.size,
cacheSize: this.cache.size()
};
}
}
//# sourceMappingURL=strategy-manager.js.map