fix: improve prompt-manager for dist build

This commit is contained in:
Ralph Khreish
2025-09-01 18:51:40 +02:00
parent 9ec0b24e84
commit 6c61472103

View File

@@ -1,18 +1,55 @@
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import { log } from './utils.js'; import { log } from './utils.js';
import Ajv from 'ajv'; import Ajv from 'ajv';
import addFormats from 'ajv-formats'; import addFormats from 'ajv-formats';
// Import all prompt templates directly
import analyzeComplexityPrompt from '../../src/prompts/analyze-complexity.json' with {
type: 'json'
};
import expandTaskPrompt from '../../src/prompts/expand-task.json' with {
type: 'json'
};
import addTaskPrompt from '../../src/prompts/add-task.json' with {
type: 'json'
};
import researchPrompt from '../../src/prompts/research.json' with {
type: 'json'
};
import parsePrdPrompt from '../../src/prompts/parse-prd.json' with {
type: 'json'
};
import updateTaskPrompt from '../../src/prompts/update-task.json' with {
type: 'json'
};
import updateTasksPrompt from '../../src/prompts/update-tasks.json' with {
type: 'json'
};
import updateSubtaskPrompt from '../../src/prompts/update-subtask.json' with {
type: 'json'
};
// Import schema for validation
import promptTemplateSchema from '../../src/prompts/schemas/prompt-template.schema.json' with {
type: 'json'
};
/** /**
* Manages prompt templates for AI interactions * Manages prompt templates for AI interactions
*/ */
export class PromptManager { export class PromptManager {
constructor() { constructor() {
const __filename = fileURLToPath(import.meta.url); // Store all prompts in a map for easy lookup
const __dirname = path.dirname(__filename); this.prompts = new Map([
this.promptsDir = path.join(__dirname, '..', '..', 'src', 'prompts'); ['analyze-complexity', analyzeComplexityPrompt],
['expand-task', expandTaskPrompt],
['add-task', addTaskPrompt],
['research', researchPrompt],
['parse-prd', parsePrdPrompt],
['update-task', updateTaskPrompt],
['update-tasks', updateTasksPrompt],
['update-subtask', updateSubtaskPrompt]
]);
this.cache = new Map(); this.cache = new Map();
this.setupValidation(); this.setupValidation();
} }
@@ -26,16 +63,8 @@ export class PromptManager {
addFormats(this.ajv); addFormats(this.ajv);
try { try {
// Load schema from src/prompts/schemas // Use the imported schema directly
const schemaPath = path.join( this.validatePrompt = this.ajv.compile(promptTemplateSchema);
this.promptsDir,
'schemas',
'prompt-template.schema.json'
);
const schemaContent = fs.readFileSync(schemaPath, 'utf-8');
const schema = JSON.parse(schemaContent);
this.validatePrompt = this.ajv.compile(schema);
log('debug', '✓ JSON schema validation enabled'); log('debug', '✓ JSON schema validation enabled');
} catch (error) { } catch (error) {
log('warn', `⚠ Schema validation disabled: ${error.message}`); log('warn', `⚠ Schema validation disabled: ${error.message}`);
@@ -94,41 +123,36 @@ export class PromptManager {
} }
/** /**
* Load a prompt template from disk * Load a prompt template from the imported prompts
* @private * @private
*/ */
loadTemplate(promptId) { loadTemplate(promptId) {
const templatePath = path.join(this.promptsDir, `${promptId}.json`); // Get template from the map
const template = this.prompts.get(promptId);
try { if (!template) {
const content = fs.readFileSync(templatePath, 'utf-8'); throw new Error(`Prompt template '${promptId}' not found`);
const template = JSON.parse(content);
// Schema validation if available (do this first for detailed errors)
if (this.validatePrompt && this.validatePrompt !== true) {
const valid = this.validatePrompt(template);
if (!valid) {
const errors = this.validatePrompt.errors
.map((err) => `${err.instancePath || 'root'}: ${err.message}`)
.join(', ');
throw new Error(`Schema validation failed: ${errors}`);
}
} else {
// Fallback basic validation if no schema validation available
if (!template.id || !template.prompts || !template.prompts.default) {
throw new Error(
'Invalid template structure: missing required fields (id, prompts.default)'
);
}
}
return template;
} catch (error) {
if (error.code === 'ENOENT') {
throw new Error(`Prompt template '${promptId}' not found`);
}
throw error;
} }
// Schema validation if available (do this first for detailed errors)
if (this.validatePrompt && this.validatePrompt !== true) {
const valid = this.validatePrompt(template);
if (!valid) {
const errors = this.validatePrompt.errors
.map((err) => `${err.instancePath || 'root'}: ${err.message}`)
.join(', ');
throw new Error(`Schema validation failed: ${errors}`);
}
} else {
// Fallback basic validation if no schema validation available
if (!template.id || !template.prompts || !template.prompts.default) {
throw new Error(
'Invalid template structure: missing required fields (id, prompts.default)'
);
}
}
return template;
} }
/** /**
@@ -385,25 +409,25 @@ export class PromptManager {
validateAllPrompts() { validateAllPrompts() {
const results = { total: 0, errors: [], valid: [] }; const results = { total: 0, errors: [], valid: [] };
try { // Iterate through all imported prompts
const files = fs.readdirSync(this.promptsDir); for (const [promptId, template] of this.prompts.entries()) {
const promptFiles = files.filter((file) => file.endsWith('.json')); results.total++;
for (const file of promptFiles) { try {
const promptId = file.replace('.json', ''); // Validate the template
results.total++; if (this.validatePrompt && this.validatePrompt !== true) {
const valid = this.validatePrompt(template);
try { if (!valid) {
this.loadTemplate(promptId); const errors = this.validatePrompt.errors
results.valid.push(promptId); .map((err) => `${err.instancePath || 'root'}: ${err.message}`)
} catch (error) { .join(', ');
results.errors.push(`${promptId}: ${error.message}`); throw new Error(`Schema validation failed: ${errors}`);
}
} }
results.valid.push(promptId);
} catch (error) {
results.errors.push(`${promptId}: ${error.message}`);
} }
} catch (error) {
results.errors.push(
`Failed to read templates directory: ${error.message}`
);
} }
return results; return results;
@@ -413,45 +437,46 @@ export class PromptManager {
* List all available prompt templates * List all available prompt templates
*/ */
listPrompts() { listPrompts() {
try { const prompts = [];
const files = fs.readdirSync(this.promptsDir);
const prompts = [];
for (const file of files) { // Iterate through all imported prompts
if (!file.endsWith('.json')) continue; for (const [promptId, template] of this.prompts.entries()) {
try {
const promptId = file.replace('.json', ''); prompts.push({
try { id: template.id,
const template = this.loadTemplate(promptId); description: template.description,
prompts.push({ version: template.version,
id: template.id, parameters: template.parameters,
description: template.description, tags: template.metadata?.tags || []
version: template.version, });
parameters: template.parameters, } catch (error) {
tags: template.metadata?.tags || [] log('warn', `Failed to process template ${promptId}: ${error.message}`);
});
} catch (error) {
log('warn', `Failed to load template ${promptId}: ${error.message}`);
}
} }
return prompts;
} catch (error) {
if (error.code === 'ENOENT') {
// Templates directory doesn't exist yet
return [];
}
throw error;
} }
return prompts;
} }
/** /**
* Validate template structure * Validate template structure
* @param {string|Object} templateOrId - Either a template ID or a template object
*/ */
validateTemplate(templatePath) { validateTemplate(templateOrId) {
try { try {
const content = fs.readFileSync(templatePath, 'utf-8'); let template;
const template = JSON.parse(content);
// Handle both template ID and direct template object
if (typeof templateOrId === 'string') {
template = this.prompts.get(templateOrId);
if (!template) {
return {
valid: false,
error: `Template '${templateOrId}' not found`
};
}
} else {
template = templateOrId;
}
// Check required fields // Check required fields
const required = ['id', 'version', 'description', 'prompts']; const required = ['id', 'version', 'description', 'prompts'];