BOLT / lib /context-selector.ts
legends810's picture
Upload folder using huggingface_hub
d83e271 verified
import { FileManifest, EditIntent, EditType } from '@/types/file-manifest';
import { analyzeEditIntent } from '@/lib/edit-intent-analyzer';
import { getEditExamplesPrompt, getComponentPatternPrompt } from '@/lib/edit-examples';
export interface FileContext {
primaryFiles: string[]; // Files to edit
contextFiles: string[]; // Files to include for reference
systemPrompt: string; // Enhanced prompt with file info
editIntent: EditIntent;
}
/**
* Select files and build context based on user prompt
*/
export function selectFilesForEdit(
userPrompt: string,
manifest: FileManifest
): FileContext {
// Analyze the edit intent
const editIntent = analyzeEditIntent(userPrompt, manifest);
// Get the files based on intent - only edit target files, but provide all others as context
const primaryFiles = editIntent.targetFiles;
const allFiles = Object.keys(manifest.files);
let contextFiles = allFiles.filter(file => !primaryFiles.includes(file));
// ALWAYS include key files in context if they exist and aren't already primary files
const keyFiles: string[] = [];
// App.jsx is most important - shows component structure
const appFile = allFiles.find(f => f.endsWith('App.jsx') || f.endsWith('App.tsx'));
if (appFile && !primaryFiles.includes(appFile)) {
keyFiles.push(appFile);
}
// Include design system files for style context
const tailwindConfig = allFiles.find(f => f.endsWith('tailwind.config.js') || f.endsWith('tailwind.config.ts'));
if (tailwindConfig && !primaryFiles.includes(tailwindConfig)) {
keyFiles.push(tailwindConfig);
}
const indexCss = allFiles.find(f => f.endsWith('index.css') || f.endsWith('globals.css'));
if (indexCss && !primaryFiles.includes(indexCss)) {
keyFiles.push(indexCss);
}
// Include package.json to understand dependencies
const packageJson = allFiles.find(f => f.endsWith('package.json'));
if (packageJson && !primaryFiles.includes(packageJson)) {
keyFiles.push(packageJson);
}
// Put key files at the beginning of context for visibility
contextFiles = [...keyFiles, ...contextFiles.filter(f => !keyFiles.includes(f))];
// Build enhanced system prompt
const systemPrompt = buildSystemPrompt(
userPrompt,
editIntent,
primaryFiles,
contextFiles,
manifest
);
return {
primaryFiles,
contextFiles,
systemPrompt,
editIntent,
};
}
/**
* Build an enhanced system prompt with file structure context
*/
function buildSystemPrompt(
userPrompt: string,
editIntent: EditIntent,
primaryFiles: string[],
contextFiles: string[],
manifest: FileManifest
): string {
const sections: string[] = [];
// Add edit examples first for better understanding
if (editIntent.type !== EditType.FULL_REBUILD) {
sections.push(getEditExamplesPrompt());
}
// Add edit intent section
sections.push(`## Edit Intent
Type: ${editIntent.type}
Description: ${editIntent.description}
Confidence: ${(editIntent.confidence * 100).toFixed(0)}%
User Request: "${userPrompt}"`);
// Add file structure overview
sections.push(buildFileStructureSection(manifest));
// Add component patterns
const fileList = Object.keys(manifest.files).map(f => f.replace('/home/user/app/', '')).join('\n');
sections.push(getComponentPatternPrompt(fileList));
// Add primary files section
if (primaryFiles.length > 0) {
sections.push(`## Files to Edit
${primaryFiles.map(f => {
const fileInfo = manifest.files[f];
return `- ${f}${fileInfo?.componentInfo ? ` (${fileInfo.componentInfo.name} component)` : ''}`;
}).join('\n')}`);
}
// Add context files section
if (contextFiles.length > 0) {
sections.push(`## Context Files (for reference only)
${contextFiles.map(f => {
const fileInfo = manifest.files[f];
return `- ${f}${fileInfo?.componentInfo ? ` (${fileInfo.componentInfo.name} component)` : ''}`;
}).join('\n')}`);
}
// Add specific instructions based on edit type
sections.push(buildEditInstructions(editIntent.type));
// Add component relationships if relevant
if (editIntent.type === EditType.UPDATE_COMPONENT ||
editIntent.type === EditType.ADD_FEATURE) {
sections.push(buildComponentRelationships(primaryFiles, manifest));
}
return sections.join('\n\n');
}
/**
* Build file structure overview section
*/
function buildFileStructureSection(manifest: FileManifest): string {
const allFiles = Object.entries(manifest.files)
.map(([path]) => path.replace('/home/user/app/', ''))
.filter(path => !path.includes('node_modules'))
.sort();
const componentFiles = Object.entries(manifest.files)
.filter(([, info]) => info.type === 'component' || info.type === 'page')
.map(([path, info]) => ({
path: path.replace('/home/user/app/', ''),
name: info.componentInfo?.name || path.split('/').pop(),
type: info.type,
}));
return `## 🚨 EXISTING PROJECT FILES - DO NOT CREATE NEW FILES WITH SIMILAR NAMES 🚨
### ALL PROJECT FILES (${allFiles.length} files)
\`\`\`
${allFiles.join('\n')}
\`\`\`
### Component Files (USE THESE EXACT NAMES)
${componentFiles.map(f =>
`- ${f.name}${f.path} (${f.type})`
).join('\n')}
### CRITICAL: Component Relationships
**ALWAYS CHECK App.jsx FIRST** to understand what components exist and how they're imported!
Common component overlaps to watch for:
- "nav" or "navigation" → Often INSIDE Header.jsx, not a separate file
- "menu" → Usually part of Header/Nav, not separate
- "logo" → Typically in Header, not standalone
When user says "nav" or "navigation":
1. First check if Header.jsx exists
2. Look inside Header.jsx for navigation elements
3. Only create Nav.jsx if navigation doesn't exist anywhere
Entry Point: ${manifest.entryPoint}
### Routes
${manifest.routes.map(r =>
`- ${r.path}${r.component.split('/').pop()}`
).join('\n') || 'No routes detected'}`;
}
/**
* Build edit-type specific instructions
*/
function buildEditInstructions(editType: EditType): string {
const instructions: Record<EditType, string> = {
[EditType.UPDATE_COMPONENT]: `## SURGICAL EDIT INSTRUCTIONS
- You MUST preserve 99% of the original code
- ONLY edit the specific component(s) mentioned
- Make ONLY the minimal change requested
- DO NOT rewrite or refactor unless explicitly asked
- DO NOT remove any existing code unless explicitly asked
- DO NOT change formatting or structure
- Preserve all imports and exports
- Maintain the existing code style
- Return the COMPLETE file with the surgical change applied
- Think of yourself as a surgeon making a precise incision, not an artist repainting`,
[EditType.ADD_FEATURE]: `## Instructions
- Create new components in appropriate directories
- IMPORTANT: Update parent components to import and use the new component
- Update routing if adding new pages
- Follow existing patterns and conventions
- Add necessary styles to match existing design
- Example workflow:
1. Create NewComponent.jsx
2. Import it in the parent: import NewComponent from './NewComponent'
3. Use it in the parent's render: <NewComponent />`,
[EditType.FIX_ISSUE]: `## Instructions
- Identify and fix the specific issue
- Test the fix doesn't break other functionality
- Preserve existing behavior except for the bug
- Add error handling if needed`,
[EditType.UPDATE_STYLE]: `## SURGICAL STYLE EDIT INSTRUCTIONS
- Change ONLY the specific style/class mentioned
- If user says "change background to blue", change ONLY the background class
- DO NOT touch any other styles, classes, or attributes
- DO NOT refactor or "improve" the styling
- DO NOT change the component structure
- Preserve ALL other classes and styles exactly as they are
- Return the COMPLETE file with only the specific style change`,
[EditType.REFACTOR]: `## Instructions
- Improve code quality without changing functionality
- Follow project conventions
- Maintain all existing features
- Improve readability and maintainability`,
[EditType.FULL_REBUILD]: `## Instructions
- You may rebuild the entire application
- Keep the same core functionality
- Improve upon the existing design
- Use modern best practices`,
[EditType.ADD_DEPENDENCY]: `## Instructions
- Update package.json with new dependency
- Add necessary import statements
- Configure the dependency if needed
- Update any build configuration`,
};
return instructions[editType] || instructions[EditType.UPDATE_COMPONENT];
}
/**
* Build component relationship information
*/
function buildComponentRelationships(
files: string[],
manifest: FileManifest
): string {
const relationships: string[] = ['## Component Relationships'];
for (const file of files) {
const fileInfo = manifest.files[file];
if (!fileInfo?.componentInfo) continue;
const componentName = fileInfo.componentInfo.name;
const treeNode = manifest.componentTree[componentName];
if (treeNode) {
relationships.push(`\n### ${componentName}`);
if (treeNode.imports.length > 0) {
relationships.push(`Imports: ${treeNode.imports.join(', ')}`);
}
if (treeNode.importedBy.length > 0) {
relationships.push(`Used by: ${treeNode.importedBy.join(', ')}`);
}
if (fileInfo.componentInfo.childComponents?.length) {
relationships.push(`Renders: ${fileInfo.componentInfo.childComponents.join(', ')}`);
}
}
}
return relationships.join('\n');
}
/**
* Get file content for selected files
*/
export async function getFileContents(
files: string[],
manifest: FileManifest
): Promise<Record<string, string>> {
const contents: Record<string, string> = {};
for (const file of files) {
const fileInfo = manifest.files[file];
if (fileInfo) {
contents[file] = fileInfo.content;
}
}
return contents;
}
/**
* Format files for AI context
*/
export function formatFilesForAI(
primaryFiles: Record<string, string>,
contextFiles: Record<string, string>
): string {
const sections: string[] = [];
// Add primary files
sections.push('## Files to Edit (ONLY OUTPUT THESE FILES)\n');
sections.push('🚨 You MUST ONLY generate the files listed below. Do NOT generate any other files! 🚨\n');
sections.push('⚠️ CRITICAL: Return the COMPLETE file - NEVER truncate with "..." or skip any lines! ⚠️\n');
sections.push('The file MUST include ALL imports, ALL functions, ALL JSX, and ALL closing tags.\n\n');
for (const [path, content] of Object.entries(primaryFiles)) {
sections.push(`### ${path}
**IMPORTANT: This is the COMPLETE file. Your output must include EVERY line shown below, modified only where necessary.**
\`\`\`${getFileExtension(path)}
${content}
\`\`\`
`);
}
// Add context files if any - but truncate large files
if (Object.keys(contextFiles).length > 0) {
sections.push('\n## Context Files (Reference Only - Do Not Edit)\n');
for (const [path, content] of Object.entries(contextFiles)) {
// Truncate very large context files to save tokens
let truncatedContent = content;
if (content.length > 2000) {
truncatedContent = content.substring(0, 2000) + '\n// ... [truncated for context length]';
}
sections.push(`### ${path}
\`\`\`${getFileExtension(path)}
${truncatedContent}
\`\`\`
`);
}
}
return sections.join('\n');
}
/**
* Get file extension for syntax highlighting
*/
function getFileExtension(path: string): string {
const ext = path.split('.').pop() || '';
const mapping: Record<string, string> = {
'js': 'javascript',
'jsx': 'javascript',
'ts': 'typescript',
'tsx': 'typescript',
'css': 'css',
'json': 'json',
};
return mapping[ext] || ext;
}