From dbc21c8f730a883e3953cf1240e5639b2061bd95 Mon Sep 17 00:00:00 2001 From: Kacper Date: Wed, 24 Dec 2025 00:09:28 +0100 Subject: [PATCH 1/2] Changes from feat/improve-ai-suggestions --- .../suggestions/generate-suggestions.ts | 85 ++++++++++++++++++- 1 file changed, 84 insertions(+), 1 deletion(-) diff --git a/apps/server/src/routes/suggestions/generate-suggestions.ts b/apps/server/src/routes/suggestions/generate-suggestions.ts index c8000ce5..8362ab2f 100644 --- a/apps/server/src/routes/suggestions/generate-suggestions.ts +++ b/apps/server/src/routes/suggestions/generate-suggestions.ts @@ -6,9 +6,87 @@ import { query } from '@anthropic-ai/claude-agent-sdk'; import type { EventEmitter } from '../../lib/events.js'; import { createLogger } from '@automaker/utils'; import { createSuggestionsOptions } from '../../lib/sdk-options.js'; +import { FeatureLoader } from '../../services/feature-loader.js'; +import { getAppSpecPath } from '@automaker/platform'; +import * as secureFs from '../../lib/secure-fs.js'; const logger = createLogger('Suggestions'); +/** + * Extract implemented features from app_spec.txt XML content + */ +function extractImplementedFeatures(specContent: string): string[] { + const features: string[] = []; + + // Match ... section + const implementedMatch = specContent.match( + /([\s\S]*?)<\/implemented_features>/ + ); + + if (implementedMatch) { + const implementedSection = implementedMatch[1]; + + // Extract feature names from ... tags + const nameRegex = /(.*?)<\/name>/g; + let match; + + while ((match = nameRegex.exec(implementedSection)) !== null) { + features.push(match[1].trim()); + } + } + + return features; +} + +/** + * Load existing context (app spec and backlog features) to avoid duplicates + */ +async function loadExistingContext(projectPath: string): Promise { + let context = ''; + + // 1. Read app_spec.txt for implemented features + try { + const appSpecPath = getAppSpecPath(projectPath); + const specContent = (await secureFs.readFile(appSpecPath, 'utf-8')) as string; + + if (specContent && specContent.trim().length > 0) { + const implementedFeatures = extractImplementedFeatures(specContent); + + if (implementedFeatures.length > 0) { + context += '\n\n=== ALREADY IMPLEMENTED FEATURES ===\n'; + context += 'These features are already implemented in the codebase:\n'; + implementedFeatures.forEach((feature) => { + context += `- ${feature}\n`; + }); + } + } + } catch (error) { + // app_spec.txt doesn't exist or can't be read - that's okay + logger.debug('No app_spec.txt found or error reading it:', error); + } + + // 2. Load existing features from backlog + try { + const featureLoader = new FeatureLoader(); + const features = await featureLoader.getAll(projectPath); + + if (features.length > 0) { + context += '\n\n=== EXISTING FEATURES IN BACKLOG ===\n'; + context += 'These features are already planned or in progress:\n'; + features.forEach((feature) => { + const status = feature.status || 'pending'; + const title = feature.title || feature.description.substring(0, 50); + context += `- ${title} (${status})\n`; + }); + } + } catch (error) { + // Features directory doesn't exist or can't be read - that's okay + logger.debug('No features found or error loading them:', error); + } + + return context; +} + /** * JSON Schema for suggestions output */ @@ -51,8 +129,13 @@ export async function generateSuggestions( performance: 'Analyze this project for performance issues and suggest optimizations.', }; - const prompt = `${typePrompts[suggestionType] || typePrompts.features} + // Load existing context to avoid duplicates + const existingContext = await loadExistingContext(projectPath); + const prompt = `${typePrompts[suggestionType] || typePrompts.features} +${existingContext} + +${existingContext ? '\nIMPORTANT: Do NOT suggest features that are already implemented or already in the backlog above. Focus on NEW ideas that complement what already exists.\n' : ''} Look at the codebase and provide 3-5 concrete suggestions. For each suggestion, provide: From e07fba13d86b02c095477d6b2bc66836ff19c6f2 Mon Sep 17 00:00:00 2001 From: Kacper Date: Wed, 24 Dec 2025 00:18:46 +0100 Subject: [PATCH 2/2] fix: adress pr reviews suggestions --- .../suggestions/generate-suggestions.ts | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/apps/server/src/routes/suggestions/generate-suggestions.ts b/apps/server/src/routes/suggestions/generate-suggestions.ts index 8362ab2f..e36e0a36 100644 --- a/apps/server/src/routes/suggestions/generate-suggestions.ts +++ b/apps/server/src/routes/suggestions/generate-suggestions.ts @@ -14,6 +14,10 @@ const logger = createLogger('Suggestions'); /** * Extract implemented features from app_spec.txt XML content + * + * Note: This uses regex-based parsing which is sufficient for our controlled + * XML structure. If more complex XML parsing is needed in the future, consider + * using a library like 'fast-xml-parser' or 'xml2js'. */ function extractImplementedFeatures(specContent: string): string[] { const features: string[] = []; @@ -26,11 +30,11 @@ function extractImplementedFeatures(specContent: string): string[] { if (implementedMatch) { const implementedSection = implementedMatch[1]; - // Extract feature names from ... tags + // Extract feature names from ... tags using matchAll const nameRegex = /(.*?)<\/name>/g; - let match; + const matches = implementedSection.matchAll(nameRegex); - while ((match = nameRegex.exec(implementedSection)) !== null) { + for (const match of matches) { features.push(match[1].trim()); } } @@ -55,9 +59,7 @@ async function loadExistingContext(projectPath: string): Promise { if (implementedFeatures.length > 0) { context += '\n\n=== ALREADY IMPLEMENTED FEATURES ===\n'; context += 'These features are already implemented in the codebase:\n'; - implementedFeatures.forEach((feature) => { - context += `- ${feature}\n`; - }); + context += implementedFeatures.map((feature) => `- ${feature}`).join('\n') + '\n'; } } } catch (error) { @@ -73,11 +75,14 @@ async function loadExistingContext(projectPath: string): Promise { if (features.length > 0) { context += '\n\n=== EXISTING FEATURES IN BACKLOG ===\n'; context += 'These features are already planned or in progress:\n'; - features.forEach((feature) => { - const status = feature.status || 'pending'; - const title = feature.title || feature.description.substring(0, 50); - context += `- ${title} (${status})\n`; - }); + context += + features + .map((feature) => { + const status = feature.status || 'pending'; + const title = feature.title || feature.description?.substring(0, 50) || 'Untitled'; + return `- ${title} (${status})`; + }) + .join('\n') + '\n'; } } catch (error) { // Features directory doesn't exist or can't be read - that's okay