From 4fa0923ff8924947ec14075725984a24b98b9e6c Mon Sep 17 00:00:00 2001 From: Shirone Date: Wed, 21 Jan 2026 22:08:51 +0100 Subject: [PATCH 1/4] feat(ideation): enhance model resolution and provider integration - Updated the ideation service to utilize phase settings for model resolution, improving flexibility in handling model aliases. - Introduced `getPhaseModelWithOverrides` to fetch model and provider information, allowing for dynamic adjustments based on project settings. - Enhanced logging to provide clearer insights into the model and provider being used during suggestion generation. This update streamlines the process of generating suggestions by leveraging phase-specific configurations, ensuring better alignment with user-defined settings. --- apps/server/src/services/ideation-service.ts | 32 +++++++++++++++----- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/apps/server/src/services/ideation-service.ts b/apps/server/src/services/ideation-service.ts index 0a6a84714..ae0e567ef 100644 --- a/apps/server/src/services/ideation-service.ts +++ b/apps/server/src/services/ideation-service.ts @@ -39,9 +39,13 @@ import { ProviderFactory } from '../providers/provider-factory.js'; import type { SettingsService } from './settings-service.js'; import type { FeatureLoader } from './feature-loader.js'; import { createChatOptions, validateWorkingDirectory } from '../lib/sdk-options.js'; -import { resolveModelString } from '@automaker/model-resolver'; +import { resolveModelString, resolvePhaseModel } from '@automaker/model-resolver'; import { stripProviderPrefix } from '@automaker/types'; -import { getPromptCustomization, getProviderByModelId } from '../lib/settings-helpers.js'; +import { + getPromptCustomization, + getProviderByModelId, + getPhaseModelWithOverrides, +} from '../lib/settings-helpers.js'; const logger = createLogger('IdeationService'); @@ -684,8 +688,24 @@ export class IdeationService { existingWorkContext ); - // Resolve model alias to canonical identifier (with prefix) - const modelId = resolveModelString('sonnet'); + // Get model from phase settings with provider info (suggestionsModel) + const phaseResult = await getPhaseModelWithOverrides( + 'suggestionsModel', + this.settingsService, + projectPath, + '[IdeationService]' + ); + const resolved = resolvePhaseModel(phaseResult.phaseModel); + // Resolve model alias to canonical identifier (e.g., 'sonnet' → 'claude-sonnet-4-5-20250929') + const modelId = resolveModelString(resolved.model); + const claudeCompatibleProvider = phaseResult.provider; + const credentials = phaseResult.credentials; + + logger.info( + 'generateSuggestions using model:', + modelId, + claudeCompatibleProvider ? `via provider: ${claudeCompatibleProvider.name}` : 'direct API' + ); // Create SDK options const sdkOptions = createChatOptions({ @@ -700,9 +720,6 @@ export class IdeationService { // Strip provider prefix - providers need bare model IDs const bareModel = stripProviderPrefix(modelId); - // Get credentials for API calls (uses hardcoded model, no phase setting) - const credentials = await this.settingsService?.getCredentials(); - const executeOptions: ExecuteOptions = { prompt: prompt.prompt, model: bareModel, @@ -713,6 +730,7 @@ export class IdeationService { // Disable all tools - we just want text generation, not codebase analysis allowedTools: [], abortController: new AbortController(), + claudeCompatibleProvider, // Pass provider for alternative endpoint configuration credentials, // Pass credentials for resolving 'credentials' apiKeySource }; From 6c47068f7143f575fb52f955a581929869dd0559 Mon Sep 17 00:00:00 2001 From: Shirone Date: Wed, 21 Jan 2026 22:23:10 +0100 Subject: [PATCH 2/4] refactor: remove redundant resolveModelString call in ideation service MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address PR #650 review feedback from gemini-code-assist. The call to resolveModelString was redundant because resolvePhaseModel already returns the fully resolved canonical model ID. When providerId is set, it returns the provider-specific model ID unchanged; otherwise, it already calls resolveModelString internally. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- apps/server/src/services/ideation-service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/server/src/services/ideation-service.ts b/apps/server/src/services/ideation-service.ts index ae0e567ef..1035ce761 100644 --- a/apps/server/src/services/ideation-service.ts +++ b/apps/server/src/services/ideation-service.ts @@ -696,8 +696,8 @@ export class IdeationService { '[IdeationService]' ); const resolved = resolvePhaseModel(phaseResult.phaseModel); - // Resolve model alias to canonical identifier (e.g., 'sonnet' → 'claude-sonnet-4-5-20250929') - const modelId = resolveModelString(resolved.model); + // resolvePhaseModel returns the canonical model identifier (e.g., 'sonnet' → 'claude-sonnet-4-5-20250929') + const modelId = resolved.model; const claudeCompatibleProvider = phaseResult.provider; const credentials = phaseResult.credentials; From 103c6bc8a0d723a44f68c8f5846c6717d82e8d7c Mon Sep 17 00:00:00 2001 From: Shirone Date: Wed, 21 Jan 2026 22:26:01 +0100 Subject: [PATCH 3/4] docs: improve comment clarity for resolvePhaseModel usage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updated the comment to better explain why resolveModelString is not needed after resolvePhaseModel - the latter already handles model alias resolution internally. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- apps/server/src/services/ideation-service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/server/src/services/ideation-service.ts b/apps/server/src/services/ideation-service.ts index 1035ce761..aa6790c66 100644 --- a/apps/server/src/services/ideation-service.ts +++ b/apps/server/src/services/ideation-service.ts @@ -696,7 +696,7 @@ export class IdeationService { '[IdeationService]' ); const resolved = resolvePhaseModel(phaseResult.phaseModel); - // resolvePhaseModel returns the canonical model identifier (e.g., 'sonnet' → 'claude-sonnet-4-5-20250929') + // resolvePhaseModel already resolves model aliases internally - no need to call resolveModelString again const modelId = resolved.model; const claudeCompatibleProvider = phaseResult.provider; const credentials = phaseResult.credentials; From 40950b5fce7b5c142acdef43131c4ef01c2231ad Mon Sep 17 00:00:00 2001 From: Shirone Date: Wed, 21 Jan 2026 23:42:53 +0100 Subject: [PATCH 4/4] refactor: remove suggestions routes and related logic This commit removes the suggestions routes and associated files from the server, streamlining the codebase. The `suggestionsModel` has been replaced with `ideationModel` across various components, including UI and service layers, to better reflect the updated functionality. Additionally, adjustments were made to ensure that the ideation service correctly utilizes the new model configuration. - Deleted suggestions routes and their handlers. - Updated references from `suggestionsModel` to `ideationModel` in settings and UI components. - Refactored related logic in the ideation service to align with the new model structure. --- apps/server/src/index.ts | 2 - apps/server/src/providers/cursor-provider.ts | 16 +- apps/server/src/routes/suggestions/common.ts | 34 -- .../suggestions/generate-suggestions.ts | 335 ------------------ apps/server/src/routes/suggestions/index.ts | 28 -- .../src/routes/suggestions/routes/generate.ts | 75 ---- .../src/routes/suggestions/routes/status.ts | 18 - .../src/routes/suggestions/routes/stop.ts | 22 -- apps/server/src/services/ideation-service.ts | 5 +- apps/ui/scripts/setup-e2e-fixtures.mjs | 2 +- .../ideation-view/components/prompt-list.tsx | 27 +- .../project-bulk-replace-dialog.tsx | 2 +- .../project-models-section.tsx | 6 +- .../model-defaults/bulk-replace-dialog.tsx | 2 +- .../model-defaults/model-defaults-section.tsx | 6 +- .../hooks/mutations/use-ideation-mutations.ts | 38 +- apps/ui/src/hooks/use-settings-sync.ts | 2 +- apps/ui/src/lib/electron.ts | 258 -------------- apps/ui/src/lib/http-api-client.ts | 20 -- libs/prompts/src/defaults.ts | 10 +- libs/types/src/event.ts | 1 - libs/types/src/settings.ts | 6 +- 22 files changed, 70 insertions(+), 845 deletions(-) delete mode 100644 apps/server/src/routes/suggestions/common.ts delete mode 100644 apps/server/src/routes/suggestions/generate-suggestions.ts delete mode 100644 apps/server/src/routes/suggestions/index.ts delete mode 100644 apps/server/src/routes/suggestions/routes/generate.ts delete mode 100644 apps/server/src/routes/suggestions/routes/status.ts delete mode 100644 apps/server/src/routes/suggestions/routes/stop.ts diff --git a/apps/server/src/index.ts b/apps/server/src/index.ts index 9fbc5375a..653baeda0 100644 --- a/apps/server/src/index.ts +++ b/apps/server/src/index.ts @@ -43,7 +43,6 @@ import { createEnhancePromptRoutes } from './routes/enhance-prompt/index.js'; import { createWorktreeRoutes } from './routes/worktree/index.js'; import { createGitRoutes } from './routes/git/index.js'; import { createSetupRoutes } from './routes/setup/index.js'; -import { createSuggestionsRoutes } from './routes/suggestions/index.js'; import { createModelsRoutes } from './routes/models/index.js'; import { createRunningAgentsRoutes } from './routes/running-agents/index.js'; import { createWorkspaceRoutes } from './routes/workspace/index.js'; @@ -331,7 +330,6 @@ app.use('/api/auto-mode', createAutoModeRoutes(autoModeService)); app.use('/api/enhance-prompt', createEnhancePromptRoutes(settingsService)); app.use('/api/worktree', createWorktreeRoutes(events, settingsService)); app.use('/api/git', createGitRoutes()); -app.use('/api/suggestions', createSuggestionsRoutes(events, settingsService)); app.use('/api/models', createModelsRoutes()); app.use('/api/spec-regeneration', createSpecRegenerationRoutes(events, settingsService)); app.use('/api/running-agents', createRunningAgentsRoutes(autoModeService)); diff --git a/apps/server/src/providers/cursor-provider.ts b/apps/server/src/providers/cursor-provider.ts index 6cefc279f..8e62ce045 100644 --- a/apps/server/src/providers/cursor-provider.ts +++ b/apps/server/src/providers/cursor-provider.ts @@ -337,10 +337,11 @@ export class CursorProvider extends CliProvider { '--stream-partial-output' // Real-time streaming ); - // Only add --force if NOT in read-only mode - // Without --force, Cursor CLI suggests changes but doesn't apply them - // With --force, Cursor CLI can actually edit files - if (!options.readOnly) { + // In read-only mode, use --mode ask for Q&A style (no tools) + // Otherwise, add --force to allow file edits + if (options.readOnly) { + cliArgs.push('--mode', 'ask'); + } else { cliArgs.push('--force'); } @@ -672,10 +673,13 @@ export class CursorProvider extends CliProvider { ); } + // Embed system prompt into user prompt (Cursor CLI doesn't support separate system messages) + const effectiveOptions = this.embedSystemPromptIntoPrompt(options); + // Extract prompt text to pass via stdin (avoids shell escaping issues) - const promptText = this.extractPromptText(options); + const promptText = this.extractPromptText(effectiveOptions); - const cliArgs = this.buildCliArgs(options); + const cliArgs = this.buildCliArgs(effectiveOptions); const subprocessOptions = this.buildSubprocessOptions(options, cliArgs); // Pass prompt via stdin to avoid shell interpretation of special characters diff --git a/apps/server/src/routes/suggestions/common.ts b/apps/server/src/routes/suggestions/common.ts deleted file mode 100644 index e4e3dbe81..000000000 --- a/apps/server/src/routes/suggestions/common.ts +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Common utilities and state for suggestions routes - */ - -import { createLogger } from '@automaker/utils'; -import { getErrorMessage as getErrorMessageShared, createLogError } from '../common.js'; - -const logger = createLogger('Suggestions'); - -// Shared state for tracking generation status - private -let isRunning = false; -let currentAbortController: AbortController | null = null; - -/** - * Get the current running state - */ -export function getSuggestionsStatus(): { - isRunning: boolean; - currentAbortController: AbortController | null; -} { - return { isRunning, currentAbortController }; -} - -/** - * Set the running state and abort controller - */ -export function setRunningState(running: boolean, controller: AbortController | null = null): void { - isRunning = running; - currentAbortController = controller; -} - -// Re-export shared utilities -export { getErrorMessageShared as getErrorMessage }; -export const logError = createLogError(logger); diff --git a/apps/server/src/routes/suggestions/generate-suggestions.ts b/apps/server/src/routes/suggestions/generate-suggestions.ts deleted file mode 100644 index b828a4ab1..000000000 --- a/apps/server/src/routes/suggestions/generate-suggestions.ts +++ /dev/null @@ -1,335 +0,0 @@ -/** - * Business logic for generating suggestions - * - * Model is configurable via phaseModels.suggestionsModel in settings - * (AI Suggestions in the UI). Supports both Claude and Cursor models. - */ - -import type { EventEmitter } from '../../lib/events.js'; -import { createLogger } from '@automaker/utils'; -import { DEFAULT_PHASE_MODELS, isCursorModel, type ThinkingLevel } from '@automaker/types'; -import { resolvePhaseModel } from '@automaker/model-resolver'; -import { extractJsonWithArray } from '../../lib/json-extractor.js'; -import { streamingQuery } from '../../providers/simple-query-service.js'; -import { FeatureLoader } from '../../services/feature-loader.js'; -import { getAppSpecPath } from '@automaker/platform'; -import * as secureFs from '../../lib/secure-fs.js'; -import type { SettingsService } from '../../services/settings-service.js'; -import { - getAutoLoadClaudeMdSetting, - getPromptCustomization, - getPhaseModelWithOverrides, - getProviderByModelId, -} from '../../lib/settings-helpers.js'; - -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[] = []; - - // Match ... section - const implementedMatch = specContent.match( - /([\s\S]*?)<\/implemented_features>/ - ); - - if (implementedMatch) { - const implementedSection = implementedMatch[1]; - - // Extract feature names from ... tags using matchAll - const nameRegex = /(.*?)<\/name>/g; - const matches = implementedSection.matchAll(nameRegex); - - for (const match of matches) { - 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'; - context += implementedFeatures.map((feature) => `- ${feature}`).join('\n') + '\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'; - 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 - logger.debug('No features found or error loading them:', error); - } - - return context; -} - -/** - * JSON Schema for suggestions output - */ -const suggestionsSchema = { - type: 'object', - properties: { - suggestions: { - type: 'array', - items: { - type: 'object', - properties: { - id: { type: 'string' }, - category: { type: 'string' }, - description: { type: 'string' }, - priority: { - type: 'number', - minimum: 1, - maximum: 3, - }, - reasoning: { type: 'string' }, - }, - required: ['category', 'description', 'priority', 'reasoning'], - }, - }, - }, - required: ['suggestions'], - additionalProperties: false, -}; - -export async function generateSuggestions( - projectPath: string, - suggestionType: string, - events: EventEmitter, - abortController: AbortController, - settingsService?: SettingsService, - modelOverride?: string, - thinkingLevelOverride?: ThinkingLevel -): Promise { - // Get customized prompts from settings - const prompts = await getPromptCustomization(settingsService, '[Suggestions]'); - - // Map suggestion types to their prompts - const typePrompts: Record = { - features: prompts.suggestions.featuresPrompt, - refactoring: prompts.suggestions.refactoringPrompt, - security: prompts.suggestions.securityPrompt, - performance: prompts.suggestions.performancePrompt, - }; - - // 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' : ''} -${prompts.suggestions.baseTemplate}`; - - // Don't send initial message - let the agent output speak for itself - // The first agent message will be captured as an info entry - - // Load autoLoadClaudeMd setting - const autoLoadClaudeMd = await getAutoLoadClaudeMdSetting( - projectPath, - settingsService, - '[Suggestions]' - ); - - // Get model from phase settings with provider info (AI Suggestions = suggestionsModel) - // Use override if provided, otherwise fall back to settings - let model: string; - let thinkingLevel: ThinkingLevel | undefined; - let provider: import('@automaker/types').ClaudeCompatibleProvider | undefined; - let credentials: import('@automaker/types').Credentials | undefined; - - if (modelOverride) { - // Use explicit override - resolve the model string - const resolved = resolvePhaseModel({ - model: modelOverride, - thinkingLevel: thinkingLevelOverride, - }); - model = resolved.model; - thinkingLevel = resolved.thinkingLevel; - - // Try to find a provider for this model (e.g., GLM, MiniMax models) - if (settingsService) { - const providerResult = await getProviderByModelId( - modelOverride, - settingsService, - '[Suggestions]' - ); - provider = providerResult.provider; - // Use resolved model from provider if available (maps to Claude model) - if (providerResult.resolvedModel) { - model = providerResult.resolvedModel; - } - credentials = providerResult.credentials ?? (await settingsService.getCredentials()); - } - // If no settingsService, credentials remains undefined (initialized above) - } else if (settingsService) { - // Use settings-based model with provider info - const phaseResult = await getPhaseModelWithOverrides( - 'suggestionsModel', - settingsService, - projectPath, - '[Suggestions]' - ); - const resolved = resolvePhaseModel(phaseResult.phaseModel); - model = resolved.model; - thinkingLevel = resolved.thinkingLevel; - provider = phaseResult.provider; - credentials = phaseResult.credentials; - } else { - // Fallback to defaults - const resolved = resolvePhaseModel(DEFAULT_PHASE_MODELS.suggestionsModel); - model = resolved.model; - thinkingLevel = resolved.thinkingLevel; - } - - logger.info( - '[Suggestions] Using model:', - model, - provider ? `via provider: ${provider.name}` : 'direct API' - ); - - let responseText = ''; - - // Determine if we should use structured output (Claude supports it, Cursor doesn't) - const useStructuredOutput = !isCursorModel(model); - - // Build the final prompt - for Cursor, include JSON schema instructions - let finalPrompt = prompt; - if (!useStructuredOutput) { - finalPrompt = `${prompt} - -CRITICAL INSTRUCTIONS: -1. DO NOT write any files. Return the JSON in your response only. -2. After analyzing the project, respond with ONLY a JSON object - no explanations, no markdown, just raw JSON. -3. The JSON must match this exact schema: - -${JSON.stringify(suggestionsSchema, null, 2)} - -Your entire response should be valid JSON starting with { and ending with }. No text before or after.`; - } - - // Use streamingQuery with event callbacks - const result = await streamingQuery({ - prompt: finalPrompt, - model, - cwd: projectPath, - maxTurns: 250, - allowedTools: ['Read', 'Glob', 'Grep'], - abortController, - thinkingLevel, - readOnly: true, // Suggestions only reads code, doesn't write - settingSources: autoLoadClaudeMd ? ['user', 'project', 'local'] : undefined, - claudeCompatibleProvider: provider, // Pass provider for alternative endpoint configuration - credentials, // Pass credentials for resolving 'credentials' apiKeySource - outputFormat: useStructuredOutput - ? { - type: 'json_schema', - schema: suggestionsSchema, - } - : undefined, - onText: (text) => { - responseText += text; - events.emit('suggestions:event', { - type: 'suggestions_progress', - content: text, - }); - }, - onToolUse: (tool, input) => { - events.emit('suggestions:event', { - type: 'suggestions_tool', - tool, - input, - }); - }, - }); - - // Use structured output if available, otherwise fall back to parsing text - try { - let structuredOutput: { suggestions: Array> } | null = null; - - if (result.structured_output) { - structuredOutput = result.structured_output as { - suggestions: Array>; - }; - logger.debug('Received structured output:', structuredOutput); - } else if (responseText) { - // Fallback: try to parse from text using shared extraction utility - logger.warn('No structured output received, attempting to parse from text'); - structuredOutput = extractJsonWithArray<{ suggestions: Array> }>( - responseText, - 'suggestions', - { logger } - ); - } - - if (structuredOutput && structuredOutput.suggestions) { - // Use structured output directly - events.emit('suggestions:event', { - type: 'suggestions_complete', - suggestions: structuredOutput.suggestions.map((s: Record, i: number) => ({ - ...s, - id: s.id || `suggestion-${Date.now()}-${i}`, - })), - }); - } else { - throw new Error('No valid JSON found in response'); - } - } catch (error) { - // Log the parsing error for debugging - logger.error('Failed to parse suggestions JSON from AI response:', error); - // Return generic suggestions if parsing fails - events.emit('suggestions:event', { - type: 'suggestions_complete', - suggestions: [ - { - id: `suggestion-${Date.now()}-0`, - category: 'Analysis', - description: 'Review the AI analysis output for insights', - priority: 1, - reasoning: 'The AI provided analysis but suggestions need manual review', - }, - ], - }); - } -} diff --git a/apps/server/src/routes/suggestions/index.ts b/apps/server/src/routes/suggestions/index.ts deleted file mode 100644 index 01e228790..000000000 --- a/apps/server/src/routes/suggestions/index.ts +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Suggestions routes - HTTP API for AI-powered feature suggestions - */ - -import { Router } from 'express'; -import type { EventEmitter } from '../../lib/events.js'; -import { validatePathParams } from '../../middleware/validate-paths.js'; -import { createGenerateHandler } from './routes/generate.js'; -import { createStopHandler } from './routes/stop.js'; -import { createStatusHandler } from './routes/status.js'; -import type { SettingsService } from '../../services/settings-service.js'; - -export function createSuggestionsRoutes( - events: EventEmitter, - settingsService?: SettingsService -): Router { - const router = Router(); - - router.post( - '/generate', - validatePathParams('projectPath'), - createGenerateHandler(events, settingsService) - ); - router.post('/stop', createStopHandler()); - router.get('/status', createStatusHandler()); - - return router; -} diff --git a/apps/server/src/routes/suggestions/routes/generate.ts b/apps/server/src/routes/suggestions/routes/generate.ts deleted file mode 100644 index 6ce2427b4..000000000 --- a/apps/server/src/routes/suggestions/routes/generate.ts +++ /dev/null @@ -1,75 +0,0 @@ -/** - * POST /generate endpoint - Generate suggestions - */ - -import type { Request, Response } from 'express'; -import type { EventEmitter } from '../../../lib/events.js'; -import { createLogger } from '@automaker/utils'; -import type { ThinkingLevel } from '@automaker/types'; -import { getSuggestionsStatus, setRunningState, getErrorMessage, logError } from '../common.js'; -import { generateSuggestions } from '../generate-suggestions.js'; -import type { SettingsService } from '../../../services/settings-service.js'; - -const logger = createLogger('Suggestions'); - -export function createGenerateHandler(events: EventEmitter, settingsService?: SettingsService) { - return async (req: Request, res: Response): Promise => { - try { - const { - projectPath, - suggestionType = 'features', - model, - thinkingLevel, - } = req.body as { - projectPath: string; - suggestionType?: string; - model?: string; - thinkingLevel?: ThinkingLevel; - }; - - if (!projectPath) { - res.status(400).json({ success: false, error: 'projectPath required' }); - return; - } - - const { isRunning } = getSuggestionsStatus(); - if (isRunning) { - res.json({ - success: false, - error: 'Suggestions generation is already running', - }); - return; - } - - setRunningState(true); - const abortController = new AbortController(); - setRunningState(true, abortController); - - // Start generation in background - generateSuggestions( - projectPath, - suggestionType, - events, - abortController, - settingsService, - model, - thinkingLevel - ) - .catch((error) => { - logError(error, 'Generate suggestions failed (background)'); - events.emit('suggestions:event', { - type: 'suggestions_error', - error: getErrorMessage(error), - }); - }) - .finally(() => { - setRunningState(false, null); - }); - - res.json({ success: true }); - } catch (error) { - logError(error, 'Generate suggestions failed'); - res.status(500).json({ success: false, error: getErrorMessage(error) }); - } - }; -} diff --git a/apps/server/src/routes/suggestions/routes/status.ts b/apps/server/src/routes/suggestions/routes/status.ts deleted file mode 100644 index eb135e062..000000000 --- a/apps/server/src/routes/suggestions/routes/status.ts +++ /dev/null @@ -1,18 +0,0 @@ -/** - * GET /status endpoint - Get status - */ - -import type { Request, Response } from 'express'; -import { getSuggestionsStatus, getErrorMessage, logError } from '../common.js'; - -export function createStatusHandler() { - return async (_req: Request, res: Response): Promise => { - try { - const { isRunning } = getSuggestionsStatus(); - res.json({ success: true, isRunning }); - } catch (error) { - logError(error, 'Get status failed'); - res.status(500).json({ success: false, error: getErrorMessage(error) }); - } - }; -} diff --git a/apps/server/src/routes/suggestions/routes/stop.ts b/apps/server/src/routes/suggestions/routes/stop.ts deleted file mode 100644 index f9e01fb65..000000000 --- a/apps/server/src/routes/suggestions/routes/stop.ts +++ /dev/null @@ -1,22 +0,0 @@ -/** - * POST /stop endpoint - Stop suggestions generation - */ - -import type { Request, Response } from 'express'; -import { getSuggestionsStatus, setRunningState, getErrorMessage, logError } from '../common.js'; - -export function createStopHandler() { - return async (_req: Request, res: Response): Promise => { - try { - const { currentAbortController } = getSuggestionsStatus(); - if (currentAbortController) { - currentAbortController.abort(); - } - setRunningState(false, null); - res.json({ success: true }); - } catch (error) { - logError(error, 'Stop suggestions failed'); - res.status(500).json({ success: false, error: getErrorMessage(error) }); - } - }; -} diff --git a/apps/server/src/services/ideation-service.ts b/apps/server/src/services/ideation-service.ts index aa6790c66..990a45529 100644 --- a/apps/server/src/services/ideation-service.ts +++ b/apps/server/src/services/ideation-service.ts @@ -688,9 +688,9 @@ export class IdeationService { existingWorkContext ); - // Get model from phase settings with provider info (suggestionsModel) + // Get model from phase settings with provider info (ideationModel) const phaseResult = await getPhaseModelWithOverrides( - 'suggestionsModel', + 'ideationModel', this.settingsService, projectPath, '[IdeationService]' @@ -730,6 +730,7 @@ export class IdeationService { // Disable all tools - we just want text generation, not codebase analysis allowedTools: [], abortController: new AbortController(), + readOnly: true, // Suggestions only need to return JSON, never write files claudeCompatibleProvider, // Pass provider for alternative endpoint configuration credentials, // Pass credentials for resolving 'credentials' apiKeySource }; diff --git a/apps/ui/scripts/setup-e2e-fixtures.mjs b/apps/ui/scripts/setup-e2e-fixtures.mjs index 356e419b2..6bfe55bed 100644 --- a/apps/ui/scripts/setup-e2e-fixtures.mjs +++ b/apps/ui/scripts/setup-e2e-fixtures.mjs @@ -58,7 +58,7 @@ const E2E_SETTINGS = { featureGenerationModel: { model: 'sonnet' }, backlogPlanningModel: { model: 'sonnet' }, projectAnalysisModel: { model: 'sonnet' }, - suggestionsModel: { model: 'sonnet' }, + ideationModel: { model: 'sonnet' }, }, enhancementModel: 'sonnet', validationModel: 'opus', diff --git a/apps/ui/src/components/views/ideation-view/components/prompt-list.tsx b/apps/ui/src/components/views/ideation-view/components/prompt-list.tsx index a402b8d17..8833bb303 100644 --- a/apps/ui/src/components/views/ideation-view/components/prompt-list.tsx +++ b/apps/ui/src/components/views/ideation-view/components/prompt-list.tsx @@ -11,7 +11,6 @@ import { useIdeationStore } from '@/store/ideation-store'; import { useAppStore } from '@/store/app-store'; import { useGenerateIdeationSuggestions } from '@/hooks/mutations'; import { toast } from 'sonner'; -import { useNavigate } from '@tanstack/react-router'; import type { IdeaCategory, IdeationPrompt } from '@automaker/types'; interface PromptListProps { @@ -24,10 +23,8 @@ export function PromptList({ category, onBack }: PromptListProps) { const generationJobs = useIdeationStore((s) => s.generationJobs); const setMode = useIdeationStore((s) => s.setMode); const addGenerationJob = useIdeationStore((s) => s.addGenerationJob); - const updateJobStatus = useIdeationStore((s) => s.updateJobStatus); const [loadingPromptId, setLoadingPromptId] = useState(null); const [startedPrompts, setStartedPrompts] = useState>(new Set()); - const navigate = useNavigate(); // React Query mutation const generateMutation = useGenerateIdeationSuggestions(currentProject?.path ?? ''); @@ -72,27 +69,13 @@ export function PromptList({ category, onBack }: PromptListProps) { toast.info(`Generating ideas for "${prompt.title}"...`); setMode('dashboard'); + // Start mutation - onSuccess/onError are handled at the hook level to ensure + // they fire even after this component unmounts (which happens due to setMode above) generateMutation.mutate( - { promptId: prompt.id, category }, + { promptId: prompt.id, category, jobId, promptTitle: prompt.title }, { - onSuccess: (data) => { - updateJobStatus(jobId, 'ready', data.suggestions); - toast.success(`Generated ${data.suggestions.length} ideas for "${prompt.title}"`, { - duration: 10000, - action: { - label: 'View Ideas', - onClick: () => { - setMode('dashboard'); - navigate({ to: '/ideation' }); - }, - }, - }); - setLoadingPromptId(null); - }, - onError: (error) => { - console.error('Failed to generate suggestions:', error); - updateJobStatus(jobId, 'error', undefined, error.message); - toast.error(error.message); + // Optional: reset local loading state if component is still mounted + onSettled: () => { setLoadingPromptId(null); }, } diff --git a/apps/ui/src/components/views/project-settings-view/project-bulk-replace-dialog.tsx b/apps/ui/src/components/views/project-settings-view/project-bulk-replace-dialog.tsx index c6209d5e0..526702636 100644 --- a/apps/ui/src/components/views/project-settings-view/project-bulk-replace-dialog.tsx +++ b/apps/ui/src/components/views/project-settings-view/project-bulk-replace-dialog.tsx @@ -44,7 +44,7 @@ const PHASE_LABELS: Record = { featureGenerationModel: 'Feature Generation', backlogPlanningModel: 'Backlog Planning', projectAnalysisModel: 'Project Analysis', - suggestionsModel: 'AI Suggestions', + ideationModel: 'Ideation', memoryExtractionModel: 'Memory Extraction', }; diff --git a/apps/ui/src/components/views/project-settings-view/project-models-section.tsx b/apps/ui/src/components/views/project-settings-view/project-models-section.tsx index e0e1f1bab..5102d243f 100644 --- a/apps/ui/src/components/views/project-settings-view/project-models-section.tsx +++ b/apps/ui/src/components/views/project-settings-view/project-models-section.tsx @@ -72,9 +72,9 @@ const GENERATION_TASKS: PhaseConfig[] = [ description: 'Analyzes project structure for suggestions', }, { - key: 'suggestionsModel', - label: 'AI Suggestions', - description: 'Model for feature, refactoring, security, and performance suggestions', + key: 'ideationModel', + label: 'Ideation', + description: 'Model for ideation view (generating AI suggestions)', }, ]; diff --git a/apps/ui/src/components/views/settings-view/model-defaults/bulk-replace-dialog.tsx b/apps/ui/src/components/views/settings-view/model-defaults/bulk-replace-dialog.tsx index 29be327e3..21b3f153f 100644 --- a/apps/ui/src/components/views/settings-view/model-defaults/bulk-replace-dialog.tsx +++ b/apps/ui/src/components/views/settings-view/model-defaults/bulk-replace-dialog.tsx @@ -42,7 +42,7 @@ const PHASE_LABELS: Record = { featureGenerationModel: 'Feature Generation', backlogPlanningModel: 'Backlog Planning', projectAnalysisModel: 'Project Analysis', - suggestionsModel: 'AI Suggestions', + ideationModel: 'Ideation', memoryExtractionModel: 'Memory Extraction', }; diff --git a/apps/ui/src/components/views/settings-view/model-defaults/model-defaults-section.tsx b/apps/ui/src/components/views/settings-view/model-defaults/model-defaults-section.tsx index 2fb4c9d38..9652f0741 100644 --- a/apps/ui/src/components/views/settings-view/model-defaults/model-defaults-section.tsx +++ b/apps/ui/src/components/views/settings-view/model-defaults/model-defaults-section.tsx @@ -67,9 +67,9 @@ const GENERATION_TASKS: PhaseConfig[] = [ description: 'Analyzes project structure for suggestions', }, { - key: 'suggestionsModel', - label: 'AI Suggestions', - description: 'Model for feature, refactoring, security, and performance suggestions', + key: 'ideationModel', + label: 'Ideation', + description: 'Model for ideation view (generating AI suggestions)', }, ]; diff --git a/apps/ui/src/hooks/mutations/use-ideation-mutations.ts b/apps/ui/src/hooks/mutations/use-ideation-mutations.ts index 61841d9e7..2c81b3eeb 100644 --- a/apps/ui/src/hooks/mutations/use-ideation-mutations.ts +++ b/apps/ui/src/hooks/mutations/use-ideation-mutations.ts @@ -8,7 +8,8 @@ import { useMutation, useQueryClient } from '@tanstack/react-query'; import { getElectronAPI } from '@/lib/electron'; import { queryKeys } from '@/lib/query-keys'; import { toast } from 'sonner'; -import type { IdeaCategory, IdeaSuggestion } from '@automaker/types'; +import type { IdeaCategory, AnalysisSuggestion } from '@automaker/types'; +import { useIdeationStore } from '@/store/ideation-store'; /** * Input for generating ideation suggestions @@ -16,15 +17,23 @@ import type { IdeaCategory, IdeaSuggestion } from '@automaker/types'; interface GenerateSuggestionsInput { promptId: string; category: IdeaCategory; + /** Job ID for tracking generation progress - used to update job status on completion */ + jobId: string; + /** Prompt title for toast notifications */ + promptTitle: string; } /** * Result from generating suggestions */ interface GenerateSuggestionsResult { - suggestions: IdeaSuggestion[]; + suggestions: AnalysisSuggestion[]; promptId: string; category: IdeaCategory; + /** Job ID passed through for onSuccess handler */ + jobId: string; + /** Prompt title passed through for toast notifications */ + promptTitle: string; } /** @@ -52,7 +61,7 @@ export function useGenerateIdeationSuggestions(projectPath: string) { return useMutation({ mutationFn: async (input: GenerateSuggestionsInput): Promise => { - const { promptId, category } = input; + const { promptId, category, jobId, promptTitle } = input; const api = getElectronAPI(); if (!api.ideation?.generateSuggestions) { @@ -69,14 +78,33 @@ export function useGenerateIdeationSuggestions(projectPath: string) { suggestions: result.suggestions ?? [], promptId, category, + jobId, + promptTitle, }; }, - onSuccess: () => { + onSuccess: (data) => { + // Update job status in Zustand store - this runs even if the component unmounts + // Using getState() to access store directly without hooks (safe in callbacks) + const updateJobStatus = useIdeationStore.getState().updateJobStatus; + updateJobStatus(data.jobId, 'ready', data.suggestions); + + // Show success toast + toast.success(`Generated ${data.suggestions.length} ideas for "${data.promptTitle}"`, { + duration: 10000, + }); + // Invalidate ideation ideas cache queryClient.invalidateQueries({ queryKey: queryKeys.ideation.ideas(projectPath), }); }, - // Toast notifications are handled by the component since it has access to prompt title + onError: (error, variables) => { + // Update job status to error - this runs even if the component unmounts + const updateJobStatus = useIdeationStore.getState().updateJobStatus; + updateJobStatus(variables.jobId, 'error', undefined, error.message); + + // Show error toast + toast.error(`Failed to generate ideas: ${error.message}`); + }, }); } diff --git a/apps/ui/src/hooks/use-settings-sync.ts b/apps/ui/src/hooks/use-settings-sync.ts index d4679b815..c74923877 100644 --- a/apps/ui/src/hooks/use-settings-sync.ts +++ b/apps/ui/src/hooks/use-settings-sync.ts @@ -596,7 +596,7 @@ export async function refreshSettingsFromServer(): Promise { projectAnalysisModel: migratePhaseModelEntry( serverSettings.phaseModels.projectAnalysisModel ), - suggestionsModel: migratePhaseModelEntry(serverSettings.phaseModels.suggestionsModel), + ideationModel: migratePhaseModelEntry(serverSettings.phaseModels.ideationModel), memoryExtractionModel: migratePhaseModelEntry( serverSettings.phaseModels.memoryExtractionModel ), diff --git a/apps/ui/src/lib/electron.ts b/apps/ui/src/lib/electron.ts index f3f8939bc..b32f52f16 100644 --- a/apps/ui/src/lib/electron.ts +++ b/apps/ui/src/lib/electron.ts @@ -370,40 +370,6 @@ export interface GitHubAPI { }>; } -// Feature Suggestions types -export interface FeatureSuggestion { - id: string; - category: string; - description: string; - priority: number; - reasoning: string; -} - -export interface SuggestionsEvent { - type: 'suggestions_progress' | 'suggestions_tool' | 'suggestions_complete' | 'suggestions_error'; - content?: string; - tool?: string; - input?: unknown; - suggestions?: FeatureSuggestion[]; - error?: string; -} - -export type SuggestionType = 'features' | 'refactoring' | 'security' | 'performance'; - -export interface SuggestionsAPI { - generate: ( - projectPath: string, - suggestionType?: SuggestionType - ) => Promise<{ success: boolean; error?: string }>; - stop: () => Promise<{ success: boolean; error?: string }>; - status: () => Promise<{ - success: boolean; - isRunning?: boolean; - error?: string; - }>; - onEvent: (callback: (event: SuggestionsEvent) => void) => () => void; -} - // Spec Regeneration types export type SpecRegenerationEvent = | { type: 'spec_regeneration_progress'; content: string; projectPath: string } @@ -702,7 +668,6 @@ export interface ElectronAPI { }; worktree?: WorktreeAPI; git?: GitAPI; - suggestions?: SuggestionsAPI; specRegeneration?: SpecRegenerationAPI; autoMode?: AutoModeAPI; features?: FeaturesAPI; @@ -1333,9 +1298,6 @@ const getMockElectronAPI = (): ElectronAPI => { // Mock Git API (for non-worktree operations) git: createMockGitAPI(), - // Mock Suggestions API - suggestions: createMockSuggestionsAPI(), - // Mock Spec Regeneration API specRegeneration: createMockSpecRegenerationAPI(), @@ -2604,226 +2566,6 @@ function delay(ms: number, featureId: string): Promise { }); } -// Mock Suggestions state and implementation -let mockSuggestionsRunning = false; -let mockSuggestionsCallbacks: ((event: SuggestionsEvent) => void)[] = []; -let mockSuggestionsTimeout: NodeJS.Timeout | null = null; - -function createMockSuggestionsAPI(): SuggestionsAPI { - return { - generate: async (projectPath: string, suggestionType: SuggestionType = 'features') => { - if (mockSuggestionsRunning) { - return { - success: false, - error: 'Suggestions generation is already running', - }; - } - - mockSuggestionsRunning = true; - console.log(`[Mock] Generating ${suggestionType} suggestions for: ${projectPath}`); - - // Simulate async suggestion generation - simulateSuggestionsGeneration(suggestionType); - - return { success: true }; - }, - - stop: async () => { - mockSuggestionsRunning = false; - if (mockSuggestionsTimeout) { - clearTimeout(mockSuggestionsTimeout); - mockSuggestionsTimeout = null; - } - return { success: true }; - }, - - status: async () => { - return { - success: true, - isRunning: mockSuggestionsRunning, - }; - }, - - onEvent: (callback: (event: SuggestionsEvent) => void) => { - mockSuggestionsCallbacks.push(callback); - return () => { - mockSuggestionsCallbacks = mockSuggestionsCallbacks.filter((cb) => cb !== callback); - }; - }, - }; -} - -function emitSuggestionsEvent(event: SuggestionsEvent) { - mockSuggestionsCallbacks.forEach((cb) => cb(event)); -} - -async function simulateSuggestionsGeneration(suggestionType: SuggestionType = 'features') { - const typeLabels: Record = { - features: 'feature suggestions', - refactoring: 'refactoring opportunities', - security: 'security vulnerabilities', - performance: 'performance issues', - }; - - // Emit progress events - emitSuggestionsEvent({ - type: 'suggestions_progress', - content: `Starting project analysis for ${typeLabels[suggestionType]}...\n`, - }); - - await new Promise((resolve) => { - mockSuggestionsTimeout = setTimeout(resolve, 500); - }); - if (!mockSuggestionsRunning) return; - - emitSuggestionsEvent({ - type: 'suggestions_tool', - tool: 'Glob', - input: { pattern: '**/*.{ts,tsx,js,jsx}' }, - }); - - await new Promise((resolve) => { - mockSuggestionsTimeout = setTimeout(resolve, 500); - }); - if (!mockSuggestionsRunning) return; - - emitSuggestionsEvent({ - type: 'suggestions_progress', - content: 'Analyzing codebase structure...\n', - }); - - await new Promise((resolve) => { - mockSuggestionsTimeout = setTimeout(resolve, 500); - }); - if (!mockSuggestionsRunning) return; - - emitSuggestionsEvent({ - type: 'suggestions_progress', - content: `Identifying ${typeLabels[suggestionType]}...\n`, - }); - - await new Promise((resolve) => { - mockSuggestionsTimeout = setTimeout(resolve, 500); - }); - if (!mockSuggestionsRunning) return; - - // Generate mock suggestions based on type - let mockSuggestions: FeatureSuggestion[]; - - switch (suggestionType) { - case 'refactoring': - mockSuggestions = [ - { - id: `suggestion-${Date.now()}-0`, - category: 'Code Smell', - description: 'Extract duplicate validation logic into reusable utility', - priority: 1, - reasoning: 'Reduces code duplication and improves maintainability', - }, - { - id: `suggestion-${Date.now()}-1`, - category: 'Complexity', - description: 'Break down large handleSubmit function into smaller functions', - priority: 2, - reasoning: 'Function is too long and handles multiple responsibilities', - }, - { - id: `suggestion-${Date.now()}-2`, - category: 'Architecture', - description: 'Move business logic out of React components into hooks', - priority: 3, - reasoning: 'Improves separation of concerns and testability', - }, - ]; - break; - - case 'security': - mockSuggestions = [ - { - id: `suggestion-${Date.now()}-0`, - category: 'High', - description: 'Sanitize user input before rendering to prevent XSS', - priority: 1, - reasoning: 'User input is rendered without proper sanitization', - }, - { - id: `suggestion-${Date.now()}-1`, - category: 'Medium', - description: 'Add rate limiting to authentication endpoints', - priority: 2, - reasoning: 'Prevents brute force attacks on authentication', - }, - { - id: `suggestion-${Date.now()}-2`, - category: 'Low', - description: 'Remove sensitive information from error messages', - priority: 3, - reasoning: 'Error messages may leak implementation details', - }, - ]; - break; - - case 'performance': - mockSuggestions = [ - { - id: `suggestion-${Date.now()}-0`, - category: 'Rendering', - description: 'Add React.memo to prevent unnecessary re-renders', - priority: 1, - reasoning: "Components re-render even when props haven't changed", - }, - { - id: `suggestion-${Date.now()}-1`, - category: 'Bundle Size', - description: 'Implement code splitting for route components', - priority: 2, - reasoning: 'Initial bundle is larger than necessary', - }, - { - id: `suggestion-${Date.now()}-2`, - category: 'Caching', - description: 'Add memoization for expensive computations', - priority: 3, - reasoning: 'Expensive computations run on every render', - }, - ]; - break; - - default: // "features" - mockSuggestions = [ - { - id: `suggestion-${Date.now()}-0`, - category: 'User Experience', - description: 'Add dark mode toggle with system preference detection', - priority: 1, - reasoning: 'Dark mode is a standard feature that improves accessibility and user comfort', - }, - { - id: `suggestion-${Date.now()}-1`, - category: 'Performance', - description: 'Implement lazy loading for heavy components', - priority: 2, - reasoning: 'Improves initial load time and reduces bundle size', - }, - { - id: `suggestion-${Date.now()}-2`, - category: 'Accessibility', - description: 'Add keyboard navigation support throughout the app', - priority: 3, - reasoning: 'Improves accessibility for users who rely on keyboard navigation', - }, - ]; - } - - emitSuggestionsEvent({ - type: 'suggestions_complete', - suggestions: mockSuggestions, - }); - - mockSuggestionsRunning = false; - mockSuggestionsTimeout = null; -} - // Mock Spec Regeneration state and implementation let mockSpecRegenerationRunning = false; let mockSpecRegenerationPhase = ''; diff --git a/apps/ui/src/lib/http-api-client.ts b/apps/ui/src/lib/http-api-client.ts index 3d818da3b..9dd863cfe 100644 --- a/apps/ui/src/lib/http-api-client.ts +++ b/apps/ui/src/lib/http-api-client.ts @@ -16,12 +16,9 @@ import type { SaveImageResult, AutoModeAPI, FeaturesAPI, - SuggestionsAPI, SpecRegenerationAPI, AutoModeEvent, - SuggestionsEvent, SpecRegenerationEvent, - SuggestionType, GitHubAPI, IssueValidationInput, IssueValidationEvent, @@ -550,7 +547,6 @@ export const checkSandboxEnvironment = async (): Promise<{ type EventType = | 'agent:stream' | 'auto-mode:event' - | 'suggestions:event' | 'spec-regeneration:event' | 'issue-validation:event' | 'backlog-plan:event' @@ -1981,22 +1977,6 @@ export class HttpApiClient implements ElectronAPI { this.post('/api/git/file-diff', { projectPath, filePath }), }; - // Suggestions API - suggestions: SuggestionsAPI = { - generate: ( - projectPath: string, - suggestionType?: SuggestionType, - model?: string, - thinkingLevel?: string - ) => - this.post('/api/suggestions/generate', { projectPath, suggestionType, model, thinkingLevel }), - stop: () => this.post('/api/suggestions/stop'), - status: () => this.get('/api/suggestions/status'), - onEvent: (callback: (event: SuggestionsEvent) => void) => { - return this.subscribeToEvent('suggestions:event', callback as EventCallback); - }, - }; - // Spec Regeneration API specRegeneration: SpecRegenerationAPI = { create: ( diff --git a/libs/prompts/src/defaults.ts b/libs/prompts/src/defaults.ts index 550f635da..52413b3a7 100644 --- a/libs/prompts/src/defaults.ts +++ b/libs/prompts/src/defaults.ts @@ -603,13 +603,15 @@ Focus on practical, implementable suggestions that would genuinely improve the p export const DEFAULT_SUGGESTIONS_SYSTEM_PROMPT = `You are an AI product strategist helping brainstorm feature ideas for a software project. -IMPORTANT: You do NOT have access to any tools. You CANNOT read files, search code, or run commands. -You must generate suggestions based ONLY on the project context provided below. -Do NOT say "I'll analyze" or "Let me explore" - you cannot do those things. +CRITICAL INSTRUCTIONS: +1. You do NOT have access to any tools. You CANNOT read files, search code, or run commands. +2. You must NEVER write, create, or edit any files. DO NOT use Write, Edit, or any file modification tools. +3. You must generate suggestions based ONLY on the project context provided below. +4. Do NOT say "I'll analyze" or "Let me explore" - you cannot do those things. Based on the project context and the user's prompt, generate exactly {{count}} creative and actionable feature suggestions. -YOUR RESPONSE MUST BE ONLY A JSON ARRAY - nothing else. No explanation, no preamble, no markdown code fences. +YOUR RESPONSE MUST BE ONLY A JSON ARRAY - nothing else. No explanation, no preamble, no markdown code fences. Do not create any files. Each suggestion must have this structure: { diff --git a/libs/types/src/event.ts b/libs/types/src/event.ts index 43f1d3d4e..281f88d8a 100644 --- a/libs/types/src/event.ts +++ b/libs/types/src/event.ts @@ -25,7 +25,6 @@ export type EventType = | 'project:analysis-progress' | 'project:analysis-completed' | 'project:analysis-error' - | 'suggestions:event' | 'spec-regeneration:event' | 'issue-validation:event' | 'ideation:stream' diff --git a/libs/types/src/settings.ts b/libs/types/src/settings.ts index cf2de7e47..c33036ebe 100644 --- a/libs/types/src/settings.ts +++ b/libs/types/src/settings.ts @@ -598,8 +598,8 @@ export interface PhaseModelConfig { backlogPlanningModel: PhaseModelEntry; /** Model for analyzing project structure */ projectAnalysisModel: PhaseModelEntry; - /** Model for AI suggestions (feature, refactoring, security, performance) */ - suggestionsModel: PhaseModelEntry; + /** Model for ideation view (generating AI suggestions for features, security, performance) */ + ideationModel: PhaseModelEntry; // Memory tasks - for learning extraction and memory operations /** Model for extracting learnings from completed agent sessions */ @@ -1235,7 +1235,7 @@ export const DEFAULT_PHASE_MODELS: PhaseModelConfig = { featureGenerationModel: { model: 'claude-sonnet' }, backlogPlanningModel: { model: 'claude-sonnet' }, projectAnalysisModel: { model: 'claude-sonnet' }, - suggestionsModel: { model: 'claude-sonnet' }, + ideationModel: { model: 'claude-sonnet' }, // Memory - use fast model for learning extraction (cost-effective) memoryExtractionModel: { model: 'claude-haiku' },