diff --git a/apps/server/src/index.ts b/apps/server/src/index.ts index 609be945..0e52d03b 100644 --- a/apps/server/src/index.ts +++ b/apps/server/src/index.ts @@ -219,7 +219,7 @@ app.get('/api/health/detailed', createDetailedHandler()); app.use('/api/fs', createFsRoutes(events)); app.use('/api/agent', createAgentRoutes(agentService, events)); app.use('/api/sessions', createSessionsRoutes(agentService)); -app.use('/api/features', createFeaturesRoutes(featureLoader)); +app.use('/api/features', createFeaturesRoutes(featureLoader, settingsService)); app.use('/api/auto-mode', createAutoModeRoutes(autoModeService)); app.use('/api/enhance-prompt', createEnhancePromptRoutes(settingsService)); app.use('/api/worktree', createWorktreeRoutes(events, settingsService)); diff --git a/apps/server/src/routes/features/index.ts b/apps/server/src/routes/features/index.ts index e0435f35..dd58e4aa 100644 --- a/apps/server/src/routes/features/index.ts +++ b/apps/server/src/routes/features/index.ts @@ -4,6 +4,7 @@ import { Router } from 'express'; import { FeatureLoader } from '../../services/feature-loader.js'; +import type { SettingsService } from '../../services/settings-service.js'; import { validatePathParams } from '../../middleware/validate-paths.js'; import { createListHandler } from './routes/list.js'; import { createGetHandler } from './routes/get.js'; @@ -15,7 +16,10 @@ import { createDeleteHandler } from './routes/delete.js'; import { createAgentOutputHandler, createRawOutputHandler } from './routes/agent-output.js'; import { createGenerateTitleHandler } from './routes/generate-title.js'; -export function createFeaturesRoutes(featureLoader: FeatureLoader): Router { +export function createFeaturesRoutes( + featureLoader: FeatureLoader, + settingsService?: SettingsService +): Router { const router = Router(); router.post('/list', validatePathParams('projectPath'), createListHandler(featureLoader)); @@ -35,7 +39,7 @@ export function createFeaturesRoutes(featureLoader: FeatureLoader): Router { router.post('/delete', validatePathParams('projectPath'), createDeleteHandler(featureLoader)); router.post('/agent-output', createAgentOutputHandler(featureLoader)); router.post('/raw-output', createRawOutputHandler(featureLoader)); - router.post('/generate-title', createGenerateTitleHandler()); + router.post('/generate-title', createGenerateTitleHandler(settingsService)); return router; } diff --git a/apps/server/src/routes/features/routes/generate-title.ts b/apps/server/src/routes/features/routes/generate-title.ts index a838e5aa..e7603eb8 100644 --- a/apps/server/src/routes/features/routes/generate-title.ts +++ b/apps/server/src/routes/features/routes/generate-title.ts @@ -9,6 +9,8 @@ import type { Request, Response } from 'express'; import { createLogger } from '@automaker/utils'; import { CLAUDE_MODEL_MAP } from '@automaker/model-resolver'; import { simpleQuery } from '../../../providers/simple-query-service.js'; +import type { SettingsService } from '../../../services/settings-service.js'; +import { getPromptCustomization } from '../../../lib/settings-helpers.js'; const logger = createLogger('GenerateTitle'); @@ -26,16 +28,9 @@ interface GenerateTitleErrorResponse { error: string; } -const SYSTEM_PROMPT = `You are a title generator. Your task is to create a concise, descriptive title (5-10 words max) for a software feature based on its description. - -Rules: -- Output ONLY the title, nothing else -- Keep it short and action-oriented (e.g., "Add dark mode toggle", "Fix login validation") -- Start with a verb when possible (Add, Fix, Update, Implement, Create, etc.) -- No quotes, periods, or extra formatting -- Capture the essence of the feature in a scannable way`; - -export function createGenerateTitleHandler(): (req: Request, res: Response) => Promise { +export function createGenerateTitleHandler( + settingsService?: SettingsService +): (req: Request, res: Response) => Promise { return async (req: Request, res: Response): Promise => { try { const { description } = req.body as GenerateTitleRequestBody; @@ -61,11 +56,15 @@ export function createGenerateTitleHandler(): (req: Request, res: Response) => P logger.info(`Generating title for description: ${trimmedDescription.substring(0, 50)}...`); + // Get customized prompts from settings + const prompts = await getPromptCustomization(settingsService, '[GenerateTitle]'); + const systemPrompt = prompts.titleGeneration.systemPrompt; + const userPrompt = `Generate a concise title for this feature:\n\n${trimmedDescription}`; // Use simpleQuery - provider abstraction handles all the streaming/extraction const result = await simpleQuery({ - prompt: `${SYSTEM_PROMPT}\n\n${userPrompt}`, + prompt: `${systemPrompt}\n\n${userPrompt}`, model: CLAUDE_MODEL_MAP.haiku, cwd: process.cwd(), maxTurns: 1, diff --git a/apps/server/src/routes/github/routes/validate-issue.ts b/apps/server/src/routes/github/routes/validate-issue.ts index 14de437b..e7d83d99 100644 --- a/apps/server/src/routes/github/routes/validate-issue.ts +++ b/apps/server/src/routes/github/routes/validate-issue.ts @@ -30,11 +30,11 @@ import { writeValidation } from '../../../lib/validation-storage.js'; import { streamingQuery } from '../../../providers/simple-query-service.js'; import { issueValidationSchema, - ISSUE_VALIDATION_SYSTEM_PROMPT, buildValidationPrompt, ValidationComment, ValidationLinkedPR, } from './validation-schema.js'; +import { getPromptCustomization } from '../../../lib/settings-helpers.js'; import { trySetValidationRunning, clearValidationStatus, @@ -117,13 +117,17 @@ async function runValidation( let responseText = ''; + // Get customized prompts from settings + const prompts = await getPromptCustomization(settingsService, '[ValidateIssue]'); + const issueValidationSystemPrompt = prompts.issueValidation.systemPrompt; + // Determine if we should use structured output (Claude/Codex support it, Cursor/OpenCode don't) const useStructuredOutput = isClaudeModel(model) || isCodexModel(model); // Build the final prompt - for Cursor, include system prompt and JSON schema instructions let finalPrompt = basePrompt; if (!useStructuredOutput) { - finalPrompt = `${ISSUE_VALIDATION_SYSTEM_PROMPT} + finalPrompt = `${issueValidationSystemPrompt} CRITICAL INSTRUCTIONS: 1. DO NOT write any files. Return the JSON in your response only. @@ -167,7 +171,7 @@ ${basePrompt}`; prompt: finalPrompt, model: model as string, cwd: projectPath, - systemPrompt: useStructuredOutput ? ISSUE_VALIDATION_SYSTEM_PROMPT : undefined, + systemPrompt: useStructuredOutput ? issueValidationSystemPrompt : undefined, abortController, thinkingLevel: effectiveThinkingLevel, reasoningEffort: effectiveReasoningEffort, diff --git a/apps/server/src/routes/github/routes/validation-schema.ts b/apps/server/src/routes/github/routes/validation-schema.ts index 010fcd7f..9ba48a2b 100644 --- a/apps/server/src/routes/github/routes/validation-schema.ts +++ b/apps/server/src/routes/github/routes/validation-schema.ts @@ -1,8 +1,11 @@ /** - * Issue Validation Schema and System Prompt + * Issue Validation Schema and Prompt Building * * Defines the JSON schema for Claude's structured output and - * the system prompt that guides the validation process. + * helper functions for building validation prompts. + * + * Note: The system prompt is now centralized in @automaker/prompts + * and accessed via getPromptCustomization() in validate-issue.ts */ /** @@ -82,76 +85,6 @@ export const issueValidationSchema = { additionalProperties: false, } as const; -/** - * System prompt that guides Claude in validating GitHub issues. - * Instructs the model to use read-only tools to analyze the codebase. - */ -export const ISSUE_VALIDATION_SYSTEM_PROMPT = `You are an expert code analyst validating GitHub issues against a codebase. - -Your task is to analyze a GitHub issue and determine if it's valid by scanning the codebase. - -## Validation Process - -1. **Read the issue carefully** - Understand what is being reported or requested -2. **Search the codebase** - Use Glob to find relevant files by pattern, Grep to search for keywords -3. **Examine the code** - Use Read to look at the actual implementation in relevant files -4. **Check linked PRs** - If there are linked pull requests, use \`gh pr diff \` to review the changes -5. **Form your verdict** - Based on your analysis, determine if the issue is valid - -## Verdicts - -- **valid**: The issue describes a real problem that exists in the codebase, or a clear feature request that can be implemented. The referenced files/components exist and the issue is actionable. - -- **invalid**: The issue describes behavior that doesn't exist, references non-existent files or components, is based on a misunderstanding of the code, or the described "bug" is actually expected behavior. - -- **needs_clarification**: The issue lacks sufficient detail to verify. Specify what additional information is needed in the missingInfo field. - -## For Bug Reports, Check: -- Do the referenced files/components exist? -- Does the code match what the issue describes? -- Is the described behavior actually a bug or expected? -- Can you locate the code that would cause the reported issue? - -## For Feature Requests, Check: -- Does the feature already exist? -- Is the implementation location clear? -- Is the request technically feasible given the codebase structure? - -## Analyzing Linked Pull Requests - -When an issue has linked PRs (especially open ones), you MUST analyze them: - -1. **Run \`gh pr diff \`** to see what changes the PR makes -2. **Run \`gh pr view \`** to see PR description and status -3. **Evaluate if the PR fixes the issue** - Does the diff address the reported problem? -4. **Provide a recommendation**: - - \`wait_for_merge\`: The PR appears to fix the issue correctly. No additional work needed - just wait for it to be merged. - - \`pr_needs_work\`: The PR attempts to fix the issue but is incomplete or has problems. - - \`no_pr\`: No relevant PR exists for this issue. - -5. **Include prAnalysis in your response** with: - - hasOpenPR: true/false - - prFixesIssue: true/false (based on diff analysis) - - prNumber: the PR number you analyzed - - prSummary: brief description of what the PR changes - - recommendation: one of the above values - -## Response Guidelines - -- **Always include relatedFiles** when you find relevant code -- **Set bugConfirmed to true** only if you can definitively confirm a bug exists in the code -- **Provide a suggestedFix** when you have a clear idea of how to address the issue -- **Use missingInfo** when the verdict is needs_clarification to list what's needed -- **Include prAnalysis** when there are linked PRs - this is critical for avoiding duplicate work -- **Set estimatedComplexity** to help prioritize: - - trivial: Simple text changes, one-line fixes - - simple: Small changes to one file - - moderate: Changes to multiple files or moderate logic changes - - complex: Significant refactoring or new feature implementation - - very_complex: Major architectural changes or cross-cutting concerns - -Be thorough in your analysis but focus on files that are directly relevant to the issue.`; - /** * Comment data structure for validation prompt */ diff --git a/apps/server/src/services/ideation-service.ts b/apps/server/src/services/ideation-service.ts index 81fc3de6..4ef3d8a8 100644 --- a/apps/server/src/services/ideation-service.ts +++ b/apps/server/src/services/ideation-service.ts @@ -41,6 +41,7 @@ import type { FeatureLoader } from './feature-loader.js'; import { createChatOptions, validateWorkingDirectory } from '../lib/sdk-options.js'; import { resolveModelString } from '@automaker/model-resolver'; import { stripProviderPrefix } from '@automaker/types'; +import { getPromptCustomization } from '../lib/settings-helpers.js'; const logger = createLogger('IdeationService'); @@ -195,8 +196,12 @@ export class IdeationService { // Gather existing features and ideas to prevent duplicate suggestions const existingWorkContext = await this.gatherExistingWorkContext(projectPath); + // Get customized prompts from settings + const prompts = await getPromptCustomization(this.settingsService, '[IdeationService]'); + // Build system prompt for ideation const systemPrompt = this.buildIdeationSystemPrompt( + prompts.ideation.ideationSystemPrompt, contextResult.formattedPrompt, activeSession.session.promptCategory, existingWorkContext @@ -645,8 +650,12 @@ export class IdeationService { // Gather existing features and ideas to prevent duplicates const existingWorkContext = await this.gatherExistingWorkContext(projectPath); + // Get customized prompts from settings + const prompts = await getPromptCustomization(this.settingsService, '[IdeationService]'); + // Build system prompt for structured suggestions const systemPrompt = this.buildSuggestionsSystemPrompt( + prompts.ideation.suggestionsSystemPrompt, contextPrompt, category, count, @@ -721,8 +730,14 @@ export class IdeationService { /** * Build system prompt for structured suggestion generation + * @param basePrompt - The base system prompt from settings + * @param contextFilesPrompt - Project context from loaded files + * @param category - The idea category to focus on + * @param count - Number of suggestions to generate + * @param existingWorkContext - Context about existing features/ideas */ private buildSuggestionsSystemPrompt( + basePrompt: string, contextFilesPrompt: string | undefined, category: IdeaCategory, count: number = 10, @@ -734,35 +749,18 @@ export class IdeationService { const existingWorkSection = existingWorkContext ? `\n\n${existingWorkContext}` : ''; - return `You are an AI product strategist helping brainstorm feature ideas for a software project. + // Replace placeholder {{count}} if present, otherwise append count instruction + let prompt = basePrompt; + if (prompt.includes('{{count}}')) { + prompt = prompt.replace(/\{\{count\}\}/g, String(count)); + } else { + prompt += `\n\nGenerate exactly ${count} suggestions.`; + } -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. - -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. - -Each suggestion must have this structure: -{ - "title": "Short, actionable title (max 60 chars)", - "description": "Clear description of what to build or improve (2-3 sentences)", - "rationale": "Why this is valuable - the problem it solves or opportunity it creates", - "priority": "high" | "medium" | "low" -} + return `${prompt} Focus area: ${this.getCategoryDescription(category)} -Guidelines: -- Generate exactly ${count} suggestions -- Be specific and actionable - avoid vague ideas -- Mix different priority levels (some high, some medium, some low) -- Each suggestion should be independently implementable -- Think creatively - include both obvious improvements and innovative ideas -- Consider the project's domain and target users -- IMPORTANT: Do NOT suggest features or ideas that already exist in the "Existing Features" or "Existing Ideas" sections below - ${contextSection}${existingWorkSection}`; } @@ -1269,30 +1267,11 @@ ${contextSection}${existingWorkSection}`; // ============================================================================ private buildIdeationSystemPrompt( + basePrompt: string, contextFilesPrompt: string | undefined, category?: IdeaCategory, existingWorkContext?: string ): string { - const basePrompt = `You are an AI product strategist and UX expert helping brainstorm ideas for improving a software project. - -Your role is to: -- Analyze the codebase structure and patterns -- Identify opportunities for improvement -- Suggest actionable ideas with clear rationale -- Consider user experience, technical feasibility, and business value -- Be specific and reference actual files/components when possible - -When suggesting ideas: -1. Provide a clear, concise title -2. Explain the problem or opportunity -3. Describe the proposed solution -4. Highlight the expected benefit -5. Note any dependencies or considerations - -IMPORTANT: Do NOT suggest features or ideas that already exist in the project. Check the "Existing Features" and "Existing Ideas" sections below to avoid duplicates. - -Focus on practical, implementable suggestions that would genuinely improve the product.`; - const categoryContext = category ? `\n\nFocus area: ${this.getCategoryDescription(category)}` : ''; diff --git a/libs/prompts/src/defaults.ts b/libs/prompts/src/defaults.ts index 9d6aeaa2..27aa332e 100644 --- a/libs/prompts/src/defaults.ts +++ b/libs/prompts/src/defaults.ts @@ -19,6 +19,10 @@ import type { ResolvedTitleGenerationPrompts, ResolvedIssueValidationPrompts, ResolvedIdeationPrompts, + ResolvedAppSpecPrompts, + ResolvedContextDescriptionPrompts, + ResolvedSuggestionsPrompts, + ResolvedTaskExecutionPrompts, } from '@automaker/types'; import { STATIC_PORT, SERVER_PORT } from '@automaker/types'; @@ -686,7 +690,7 @@ IMPORTANT: Do not ask for clarification. The specification is provided above. Ge /** * Default App Spec prompts (for project specification generation) */ -export const DEFAULT_APP_SPEC_PROMPTS: import('@automaker/types').ResolvedAppSpecPrompts = { +export const DEFAULT_APP_SPEC_PROMPTS: ResolvedAppSpecPrompts = { generateSpecSystemPrompt: DEFAULT_APP_SPEC_GENERATE_SYSTEM_PROMPT, structuredSpecInstructions: DEFAULT_APP_SPEC_STRUCTURED_INSTRUCTIONS, generateFeaturesFromSpecPrompt: DEFAULT_GENERATE_FEATURES_FROM_SPEC_PROMPT, @@ -709,11 +713,10 @@ Respond with ONLY the description text, no additional formatting, preamble, or e /** * Default Context Description prompts (for file/image descriptions) */ -export const DEFAULT_CONTEXT_DESCRIPTION_PROMPTS: import('@automaker/types').ResolvedContextDescriptionPrompts = - { - describeFilePrompt: DEFAULT_DESCRIBE_FILE_PROMPT, - describeImagePrompt: DEFAULT_DESCRIBE_IMAGE_PROMPT, - }; +export const DEFAULT_CONTEXT_DESCRIPTION_PROMPTS: ResolvedContextDescriptionPrompts = { + describeFilePrompt: DEFAULT_DESCRIBE_FILE_PROMPT, + describeImagePrompt: DEFAULT_DESCRIBE_IMAGE_PROMPT, +}; /** * ======================================================================== @@ -743,7 +746,7 @@ The response will be automatically formatted as structured JSON.`; /** * Default Suggestions prompts (for features, refactoring, security, performance) */ -export const DEFAULT_SUGGESTIONS_PROMPTS: import('@automaker/types').ResolvedSuggestionsPrompts = { +export const DEFAULT_SUGGESTIONS_PROMPTS: ResolvedSuggestionsPrompts = { featuresPrompt: DEFAULT_SUGGESTIONS_FEATURES_PROMPT, refactoringPrompt: DEFAULT_SUGGESTIONS_REFACTORING_PROMPT, securityPrompt: DEFAULT_SUGGESTIONS_SECURITY_PROMPT, @@ -930,18 +933,17 @@ Format your response as a structured markdown document.`; /** * Default Task Execution prompts (for Auto Mode task execution, learning extraction) */ -export const DEFAULT_TASK_EXECUTION_PROMPTS: import('@automaker/types').ResolvedTaskExecutionPrompts = - { - taskPromptTemplate: DEFAULT_TASK_PROMPT_TEMPLATE, - implementationInstructions: DEFAULT_IMPLEMENTATION_INSTRUCTIONS, - playwrightVerificationInstructions: DEFAULT_PLAYWRIGHT_VERIFICATION_INSTRUCTIONS, - learningExtractionSystemPrompt: DEFAULT_LEARNING_EXTRACTION_SYSTEM_PROMPT, - learningExtractionUserPromptTemplate: DEFAULT_LEARNING_EXTRACTION_USER_TEMPLATE, - planRevisionTemplate: DEFAULT_PLAN_REVISION_TEMPLATE, - continuationAfterApprovalTemplate: DEFAULT_CONTINUATION_AFTER_APPROVAL_TEMPLATE, - resumeFeatureTemplate: DEFAULT_RESUME_FEATURE_TEMPLATE, - projectAnalysisPrompt: DEFAULT_PROJECT_ANALYSIS_PROMPT, - }; +export const DEFAULT_TASK_EXECUTION_PROMPTS: ResolvedTaskExecutionPrompts = { + taskPromptTemplate: DEFAULT_TASK_PROMPT_TEMPLATE, + implementationInstructions: DEFAULT_IMPLEMENTATION_INSTRUCTIONS, + playwrightVerificationInstructions: DEFAULT_PLAYWRIGHT_VERIFICATION_INSTRUCTIONS, + learningExtractionSystemPrompt: DEFAULT_LEARNING_EXTRACTION_SYSTEM_PROMPT, + learningExtractionUserPromptTemplate: DEFAULT_LEARNING_EXTRACTION_USER_TEMPLATE, + planRevisionTemplate: DEFAULT_PLAN_REVISION_TEMPLATE, + continuationAfterApprovalTemplate: DEFAULT_CONTINUATION_AFTER_APPROVAL_TEMPLATE, + resumeFeatureTemplate: DEFAULT_RESUME_FEATURE_TEMPLATE, + projectAnalysisPrompt: DEFAULT_PROJECT_ANALYSIS_PROMPT, +}; /** * ========================================================================