PromptEnhancer.ts•22.2 kB
/**
* PromptEnhancer
*
* Intelligently enhances prompts with relevant context:
* - Combines original prompt with adaptive context
* - Filters context based on task type and focus area
* - Formats enhanced prompts for optimal LLM performance
*/
import ProjectContextManager, { ProjectContext } from '../context/ProjectContextManager';
import TaskTracker, { TaskType, FocusArea, TaskContext } from '../tracker/TaskTracker';
import logger from '../utils/Logger';
export interface PromptEnhancerConfig {
maxContextSize: number;
includeGitInfo: boolean;
includeDependencies: boolean;
includePatterns: boolean;
contextFormat: 'markdown' | 'json' | 'plain';
autoEvaluateNeed: boolean;
}
export interface EnhancePromptOptions {
prompt: string;
taskType?: TaskType;
focusFiles?: string[];
includeFullContext?: boolean;
}
export interface PromptEnhancementResult {
enhancedPrompt: string;
originalPrompt: string;
contextAdded: boolean;
taskType: TaskType;
focusArea: FocusArea;
contextSize: number;
}
export class PromptEnhancer {
private config: PromptEnhancerConfig;
private projectContextManager: ProjectContextManager;
private taskTracker: TaskTracker;
private log = logger.createChildLogger('PromptEnhancer');
constructor(
projectContextManager: ProjectContextManager,
taskTracker: TaskTracker,
config: Partial<PromptEnhancerConfig> = {}
) {
this.projectContextManager = projectContextManager;
this.taskTracker = taskTracker;
this.config = {
maxContextSize: config.maxContextSize || 8000,
includeGitInfo: config.includeGitInfo !== undefined ? config.includeGitInfo : true,
includeDependencies: config.includeDependencies !== undefined ? config.includeDependencies : true,
includePatterns: config.includePatterns !== undefined ? config.includePatterns : true,
contextFormat: config.contextFormat || 'markdown',
autoEvaluateNeed: config.autoEvaluateNeed !== undefined ? config.autoEvaluateNeed : true,
};
this.log.info('PromptEnhancer initialized');
}
/**
* Enhance a prompt with relevant context
*/
public async enhancePrompt(options: EnhancePromptOptions): Promise<PromptEnhancementResult> {
const { prompt, taskType, focusFiles, includeFullContext } = options;
this.log.debug('Enhancing prompt');
// Update task tracker with prompt
const taskContext = this.taskTracker.updateFromPrompt(prompt);
// Override task type if provided
if (taskType) {
this.taskTracker.setTaskType(taskType);
}
// Add focus files if provided
if (focusFiles && focusFiles.length > 0) {
this.taskTracker.addRelevantFiles(focusFiles);
}
// Get updated task context
const updatedTaskContext = this.taskTracker.getTaskContext();
// Evaluate if prompt enhancement is needed when auto-evaluation is enabled
if (this.config.autoEvaluateNeed && !this.isEnhancementNeeded(prompt, updatedTaskContext)) {
this.log.info('Prompt enhancement not needed, returning original prompt');
// Record the skip action
this.taskTracker.recordAction(
'skip_enhancement',
updatedTaskContext.relevantFiles,
`Skipped enhancement for ${updatedTaskContext.taskType} task - not needed`
);
// Return the original prompt without enhancement
return {
enhancedPrompt: prompt,
originalPrompt: prompt,
contextAdded: false,
taskType: updatedTaskContext.taskType,
focusArea: updatedTaskContext.focusArea,
contextSize: 0
};
}
// Get project context
const projectContext = await this.projectContextManager.getContext();
// Generate enhanced prompt
const enhancementResult = this.generateEnhancedPrompt(
prompt,
projectContext,
updatedTaskContext,
includeFullContext || false
);
// Record the enhancement action
this.taskTracker.recordAction(
'enhance_prompt',
updatedTaskContext.relevantFiles,
`Enhanced prompt for ${updatedTaskContext.taskType} task`
);
return enhancementResult;
}
/**
* Determine if prompt enhancement is needed
*/
private isEnhancementNeeded(prompt: string, taskContext: TaskContext): boolean {
// Always enhance prompts in these cases
const alwaysEnhanceCases = [
// First time analyzing a project or requesting full context
taskContext.recentActions.length === 0,
// Specific keywords that indicate a need for context
prompt.toLowerCase().includes('analyze'),
prompt.toLowerCase().includes('understand'),
prompt.toLowerCase().includes('review'),
prompt.toLowerCase().includes('help me with'),
prompt.toLowerCase().includes('show me'),
// Tasks likely to need context
taskContext.taskType === TaskType.ANALYSIS,
taskContext.taskType === TaskType.DEBUGGING,
taskContext.taskType === TaskType.REFACTORING
];
// Skip enhancement in these cases
const skipEnhanceCases = [
// Simple questions that don't need context
prompt.toLowerCase().includes('what is'),
prompt.toLowerCase().includes('how do i'),
prompt.toLowerCase().includes('can you explain'),
// Very short prompts (likely follow-up questions)
prompt.length < 50 && taskContext.recentActions.length > 0,
// Prompts that explicitly ask to skip context
prompt.toLowerCase().includes('no context'),
prompt.toLowerCase().includes('without context')
];
// Check if any always-enhance conditions are met
if (alwaysEnhanceCases.some(condition => !!condition)) {
this.log.debug('Enhancement needed: meets enhancement criteria');
return true;
}
// Check if any skip-enhancement conditions are met
if (skipEnhanceCases.some(condition => !!condition)) {
this.log.debug('Enhancement not needed: meets skip criteria');
return false;
}
// Default to enhancing if no skip conditions were met
return true;
}
/**
* Generate enhanced prompt with context
*/
private generateEnhancedPrompt(
originalPrompt: string,
projectContext: ProjectContext,
taskContext: TaskContext,
includeFullContext: boolean
): PromptEnhancementResult {
// Start with the context section
let contextSection = '';
if (includeFullContext) {
// Full project context for initial analysis or when explicitly requested
contextSection = this.generateFullContext(projectContext);
} else {
// Task-specific context for ongoing work
contextSection = this.generateTaskSpecificContext(projectContext, taskContext);
}
// Measure context size (rough approximation of tokens)
const contextSize = Math.ceil(contextSection.length / 4);
// Format the enhanced prompt
let enhancedPrompt = '';
if (contextSection.trim().length > 0) {
enhancedPrompt = this.formatEnhancedPrompt(originalPrompt, contextSection);
} else {
enhancedPrompt = originalPrompt;
}
return {
enhancedPrompt,
originalPrompt,
contextAdded: contextSection.trim().length > 0,
taskType: taskContext.taskType,
focusArea: taskContext.focusArea,
contextSize,
};
}
/**
* Generate full project context
*/
private generateFullContext(projectContext: ProjectContext): string {
this.log.debug('Generating full project context');
let context = '';
// Project overview
context += this.formatSection('Project Overview', [
`Project Name: ${projectContext.projectName}`,
`Frameworks: ${projectContext.frameworks.join(', ') || 'None detected'}`,
]);
// Dependencies (if enabled)
if (this.config.includeDependencies && Object.keys(projectContext.dependencies).length > 0) {
const dependencyList = Object.entries(projectContext.dependencies)
.map(([name, version]) => `${name}: ${version}`)
.join('\n');
context += this.formatSection('Dependencies', [dependencyList]);
}
// File structure
context += this.formatSection('File Structure', [
this.formatFileStructure(projectContext.fileStructure)
]);
// Git information (if enabled)
if (this.config.includeGitInfo && projectContext.recentChanges.length > 0) {
const gitChanges = projectContext.recentChanges
.map(change => `- ${change.message} (by ${change.author} on ${change.date.split('T')[0]})`)
.join('\n');
context += this.formatSection('Recent Changes', [gitChanges]);
const branchInfo = [
`Current Branch: ${projectContext.branchInfo.currentBranch}`,
`All Branches: ${projectContext.branchInfo.branches.join(', ')}`
].join('\n');
context += this.formatSection('Branch Information', [branchInfo]);
}
// Code patterns (if enabled)
if (this.config.includePatterns && projectContext.patterns.length > 0) {
const patternSections = projectContext.patterns.map(pattern => {
return `${pattern.type.toUpperCase()} PATTERN: ${pattern.pattern}\n\nExamples:\n${pattern.examples.map(ex => '```\n' + ex + '\n```').join('\n\n')}`;
}).join('\n\n');
context += this.formatSection('Code Patterns', [patternSections]);
}
return context;
}
/**
* Generate task-specific context
*/
private generateTaskSpecificContext(
projectContext: ProjectContext,
taskContext: TaskContext
): string {
this.log.debug(`Generating task-specific context for ${taskContext.taskType} task in ${taskContext.focusArea} area`);
let context = '';
// Task overview
context += this.formatSection('Task Context', [
`Task Type: ${taskContext.taskType}`,
`Focus Area: ${taskContext.focusArea}`,
]);
// Include frameworks and project type info for all tasks
context += this.formatSection('Project Type', [
`Frameworks: ${projectContext.frameworks.join(', ') || 'None detected'}`,
]);
// Add task-specific context based on task type
switch (taskContext.taskType) {
case TaskType.CREATION:
context += this.generateCreationTaskContext(projectContext, taskContext);
break;
case TaskType.DEBUGGING:
context += this.generateDebuggingTaskContext(projectContext, taskContext);
break;
case TaskType.REFACTORING:
context += this.generateRefactoringTaskContext(projectContext, taskContext);
break;
case TaskType.STYLING:
context += this.generateStylingTaskContext(projectContext, taskContext);
break;
case TaskType.API:
context += this.generateApiTaskContext(projectContext, taskContext);
break;
default:
// For general tasks or other types, include minimal context
context += this.generateGeneralTaskContext(projectContext, taskContext);
break;
}
// Add relevant files info
if (taskContext.relevantFiles.length > 0) {
context += this.formatSection('Relevant Files', [
taskContext.relevantFiles.join('\n')
]);
}
// Add recent actions if available
if (taskContext.recentActions.length > 0) {
const recentActionsText = taskContext.recentActions
.map(action => {
const filesText = action.files && action.files.length > 0
? ` (files: ${action.files.join(', ')})`
: '';
return `- ${action.description}${filesText}`;
})
.join('\n');
context += this.formatSection('Recent Actions', [recentActionsText]);
}
return context;
}
/**
* Generate context for creation tasks
*/
private generateCreationTaskContext(
projectContext: ProjectContext,
taskContext: TaskContext
): string {
let context = '';
// For creation tasks, include:
// 1. Dependencies (for setup)
if (this.config.includeDependencies) {
const relevantDeps = this.filterRelevantDependencies(projectContext, taskContext.focusArea);
if (Object.keys(relevantDeps).length > 0) {
const depsText = Object.entries(relevantDeps)
.map(([name, version]) => `${name}: ${version}`)
.join('\n');
context += this.formatSection('Relevant Dependencies', [depsText]);
}
}
// 2. Code patterns for the focus area
if (this.config.includePatterns) {
const relevantPatterns = this.filterRelevantPatterns(projectContext, taskContext.focusArea);
if (relevantPatterns.length > 0) {
const patternsText = relevantPatterns.map(pattern => {
return `${pattern.type.toUpperCase()} PATTERN: ${pattern.pattern}\n\nExample:\n\`\`\`\n${pattern.examples[0] || ''}\n\`\`\``;
}).join('\n\n');
context += this.formatSection('Relevant Patterns', [patternsText]);
}
}
return context;
}
/**
* Generate context for debugging tasks
*/
private generateDebuggingTaskContext(
projectContext: ProjectContext,
taskContext: TaskContext
): string {
let context = '';
// For debugging tasks, include:
// 1. Recent changes that might be related to the bug
if (this.config.includeGitInfo && projectContext.recentChanges.length > 0) {
const recentChangesText = projectContext.recentChanges
.slice(0, 5) // Focus on the most recent changes
.map(change => `- ${change.message} (${change.date.split('T')[0]})`)
.join('\n');
context += this.formatSection('Recent Changes', [recentChangesText]);
}
return context;
}
/**
* Generate context for refactoring tasks
*/
private generateRefactoringTaskContext(
projectContext: ProjectContext,
taskContext: TaskContext
): string {
let context = '';
// For refactoring tasks, include:
// 1. Code patterns that might be relevant
if (this.config.includePatterns) {
const relevantPatterns = this.filterRelevantPatterns(projectContext, taskContext.focusArea);
if (relevantPatterns.length > 0) {
const patternsText = relevantPatterns.map(pattern => {
return `${pattern.type.toUpperCase()}: ${pattern.pattern}\n\nExample:\n\`\`\`\n${pattern.examples[0] || ''}\n\`\`\``;
}).join('\n\n');
context += this.formatSection('Code Patterns', [patternsText]);
}
}
return context;
}
/**
* Generate context for styling tasks
*/
private generateStylingTaskContext(
projectContext: ProjectContext,
taskContext: TaskContext
): string {
let context = '';
// For styling tasks, include:
// 1. Check for styling-related dependencies
if (this.config.includeDependencies) {
const stylingDeps = this.filterStylingDependencies(projectContext);
if (Object.keys(stylingDeps).length > 0) {
const depsText = Object.entries(stylingDeps)
.map(([name, version]) => `${name}: ${version}`)
.join('\n');
context += this.formatSection('Styling Dependencies', [depsText]);
}
}
return context;
}
/**
* Generate context for API tasks
*/
private generateApiTaskContext(
projectContext: ProjectContext,
taskContext: TaskContext
): string {
let context = '';
// For API tasks, include:
// 1. API-related patterns
if (this.config.includePatterns) {
const apiPatterns = projectContext.patterns.filter(pattern =>
pattern.type === 'route' || pattern.pattern.toLowerCase().includes('api')
);
if (apiPatterns.length > 0) {
const patternsText = apiPatterns.map(pattern => {
return `${pattern.type.toUpperCase()}: ${pattern.pattern}\n\nExample:\n\`\`\`\n${pattern.examples[0] || ''}\n\`\`\``;
}).join('\n\n');
context += this.formatSection('API Patterns', [patternsText]);
}
}
return context;
}
/**
* Generate context for general tasks
*/
private generateGeneralTaskContext(
projectContext: ProjectContext,
taskContext: TaskContext
): string {
// For general tasks, minimal context is already provided by the main method
return '';
}
/**
* Filter dependencies relevant to the focus area
*/
private filterRelevantDependencies(
projectContext: ProjectContext,
focusArea: FocusArea
): Record<string, string> {
const deps = { ...projectContext.dependencies };
const filtered: Record<string, string> = {};
// Keywords for different focus areas
const keywords: Record<FocusArea, string[]> = {
[FocusArea.FRONTEND]: ['react', 'vue', 'angular', 'svelte', 'component', 'ui', 'dom'],
[FocusArea.BACKEND]: ['express', 'koa', 'nest', 'node', 'server', 'api'],
[FocusArea.DATABASE]: ['mongo', 'postgres', 'mysql', 'sqlite', 'prisma', 'sequelize', 'typeorm'],
[FocusArea.STYLING]: ['css', 'sass', 'less', 'tailwind', 'styled', 'emotion', 'bootstrap'],
[FocusArea.TESTING]: ['jest', 'mocha', 'chai', 'cypress', 'testing', 'test'],
[FocusArea.STATE]: ['redux', 'mobx', 'zustand', 'recoil', 'context', 'state'],
[FocusArea.DEPLOYMENT]: ['docker', 'kubernetes', 'aws', 'azure', 'vercel', 'netlify'],
[FocusArea.GENERAL]: [],
[FocusArea.AUTHENTICATION]: ['auth', 'login', 'register', 'user', 'permission', 'role', 'jwt', 'session'],
[FocusArea.COMPONENTS]: ['component', 'button', 'form', 'input', 'modal', 'menu', 'navigation'],
[FocusArea.ROUTING]: ['route', 'router', 'navigation', 'link', 'path', 'url', 'history', 'params'],
};
// If focus area is general, return a subset of important dependencies
if (focusArea === FocusArea.GENERAL) {
const importantDeps = Object.keys(deps).filter(dep =>
!dep.startsWith('@types/') &&
!dep.includes('eslint') &&
!dep.includes('prettier')
).slice(0, 10);
for (const dep of importantDeps) {
filtered[dep] = deps[dep];
}
return filtered;
}
// Otherwise, filter by focus area keywords
const areaKeywords = keywords[focusArea] || [];
for (const [dep, version] of Object.entries(deps)) {
if (areaKeywords.some(keyword => dep.toLowerCase().includes(keyword))) {
filtered[dep] = version;
}
}
return filtered;
}
/**
* Filter styling-related dependencies
*/
private filterStylingDependencies(projectContext: ProjectContext): Record<string, string> {
const deps = { ...projectContext.dependencies };
const filtered: Record<string, string> = {};
const stylingKeywords = [
'css', 'sass', 'scss', 'less', 'style', 'styled', 'tailwind', 'bootstrap',
'emotion', 'jss', 'material-ui', 'chakra', 'theme'
];
for (const [dep, version] of Object.entries(deps)) {
if (stylingKeywords.some(keyword => dep.toLowerCase().includes(keyword))) {
filtered[dep] = version;
}
}
return filtered;
}
/**
* Filter patterns relevant to the focus area
*/
private filterRelevantPatterns(
projectContext: ProjectContext,
focusArea: FocusArea
) {
// Map focus areas to pattern types
const focusToPatternType: Partial<Record<FocusArea, string[]>> = {
[FocusArea.FRONTEND]: ['component', 'jsx', 'tsx', 'react', 'vue'],
[FocusArea.BACKEND]: ['route', 'controller', 'service', 'middleware'],
[FocusArea.DATABASE]: ['model', 'schema', 'query', 'database'],
// Remove FocusArea.API which doesn't exist
};
const relevantTypes = focusToPatternType[focusArea] || [];
if (relevantTypes.length === 0) {
// If no specific mapping, return the first 2 patterns (if available)
return projectContext.patterns.slice(0, 2);
}
// Filter patterns by relevance to the focus area
return projectContext.patterns.filter(pattern =>
relevantTypes.some(type =>
pattern.type.includes(type) ||
pattern.pattern.toLowerCase().includes(type)
)
).slice(0, 3);
}
/**
* Format file structure into a readable string
*/
private formatFileStructure(structure: any, prefix = '', depth = 0, maxDepth = 3): string {
if (depth > maxDepth) {
return `${prefix}...`; // Truncate at max depth
}
let result = '';
// Add directories
for (const [dirName, dirContent] of Object.entries(structure.directories)) {
result += `${prefix}${dirName}/\n`;
result += this.formatFileStructure(
dirContent,
`${prefix} `,
depth + 1,
maxDepth
);
}
// Add files
for (const file of structure.files) {
result += `${prefix}${file}\n`;
}
return result;
}
/**
* Format a section of the context
*/
private formatSection(title: string, content: string[]): string {
if (content.every(item => item.trim() === '')) {
return '';
}
switch (this.config.contextFormat) {
case 'markdown':
return `## ${title}\n\n${content.join('\n\n')}\n\n`;
case 'json':
return `"${title}": ${JSON.stringify(content)},\n`;
case 'plain':
default:
return `${title.toUpperCase()}:\n${content.join('\n')}\n\n`;
}
}
/**
* Format the final enhanced prompt
*/
private formatEnhancedPrompt(originalPrompt: string, contextSection: string): string {
switch (this.config.contextFormat) {
case 'markdown':
return `# Context Information\n\n${contextSection}\n# Original Prompt\n\n${originalPrompt}`;
case 'json':
return `{\n"context": {\n${contextSection}},\n"prompt": "${originalPrompt.replace(/"/g, '\\"')}"\n}`;
case 'plain':
default:
return `CONTEXT INFORMATION:\n\n${contextSection}\nORIGINAL PROMPT:\n\n${originalPrompt}`;
}
}
}
export default PromptEnhancer;