Skip to content
Merged
2 changes: 1 addition & 1 deletion apps/server/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
24 changes: 24 additions & 0 deletions apps/server/src/lib/settings-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@ import {
mergeAgentPrompts,
mergeBacklogPlanPrompts,
mergeEnhancementPrompts,
mergeCommitMessagePrompts,
mergeTitleGenerationPrompts,
mergeIssueValidationPrompts,
mergeIdeationPrompts,
mergeAppSpecPrompts,
mergeContextDescriptionPrompts,
mergeSuggestionsPrompts,
mergeTaskExecutionPrompts,
} from '@automaker/prompts';

const logger = createLogger('SettingsHelper');
Expand Down Expand Up @@ -218,6 +226,14 @@ export async function getPromptCustomization(
agent: ReturnType<typeof mergeAgentPrompts>;
backlogPlan: ReturnType<typeof mergeBacklogPlanPrompts>;
enhancement: ReturnType<typeof mergeEnhancementPrompts>;
commitMessage: ReturnType<typeof mergeCommitMessagePrompts>;
titleGeneration: ReturnType<typeof mergeTitleGenerationPrompts>;
issueValidation: ReturnType<typeof mergeIssueValidationPrompts>;
ideation: ReturnType<typeof mergeIdeationPrompts>;
appSpec: ReturnType<typeof mergeAppSpecPrompts>;
contextDescription: ReturnType<typeof mergeContextDescriptionPrompts>;
suggestions: ReturnType<typeof mergeSuggestionsPrompts>;
taskExecution: ReturnType<typeof mergeTaskExecutionPrompts>;
}> {
let customization: PromptCustomization = {};

Expand All @@ -239,6 +255,14 @@ export async function getPromptCustomization(
agent: mergeAgentPrompts(customization.agent),
backlogPlan: mergeBacklogPlanPrompts(customization.backlogPlan),
enhancement: mergeEnhancementPrompts(customization.enhancement),
commitMessage: mergeCommitMessagePrompts(customization.commitMessage),
titleGeneration: mergeTitleGenerationPrompts(customization.titleGeneration),
issueValidation: mergeIssueValidationPrompts(customization.issueValidation),
ideation: mergeIdeationPrompts(customization.ideation),
appSpec: mergeAppSpecPrompts(customization.appSpec),
contextDescription: mergeContextDescriptionPrompts(customization.contextDescription),
suggestions: mergeSuggestionsPrompts(customization.suggestions),
taskExecution: mergeTaskExecutionPrompts(customization.taskExecution),
};
}

Expand Down
34 changes: 6 additions & 28 deletions apps/server/src/routes/app-spec/generate-features-from-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { streamingQuery } from '../../providers/simple-query-service.js';
import { parseAndCreateFeatures } from './parse-and-create-features.js';
import { getAppSpecPath } from '@automaker/platform';
import type { SettingsService } from '../../services/settings-service.js';
import { getAutoLoadClaudeMdSetting } from '../../lib/settings-helpers.js';
import { getAutoLoadClaudeMdSetting, getPromptCustomization } from '../../lib/settings-helpers.js';

const logger = createLogger('SpecRegeneration');

Expand Down Expand Up @@ -53,38 +53,16 @@ export async function generateFeaturesFromSpec(
return;
}

// Get customized prompts from settings
const prompts = await getPromptCustomization(settingsService, '[FeatureGeneration]');

const prompt = `Based on this project specification:

${spec}

Generate a prioritized list of implementable features. For each feature provide:

1. **id**: A unique lowercase-hyphenated identifier
2. **category**: Functional category (e.g., "Core", "UI", "API", "Authentication", "Database")
3. **title**: Short descriptive title
4. **description**: What this feature does (2-3 sentences)
5. **priority**: 1 (high), 2 (medium), or 3 (low)
6. **complexity**: "simple", "moderate", or "complex"
7. **dependencies**: Array of feature IDs this depends on (can be empty)

Format as JSON:
{
"features": [
{
"id": "feature-id",
"category": "Feature Category",
"title": "Feature Title",
"description": "What it does",
"priority": 1,
"complexity": "moderate",
"dependencies": []
}
]
}

Generate ${featureCount} features that build on each other logically.
${prompts.appSpec.generateFeaturesFromSpecPrompt}

IMPORTANT: Do not ask for clarification. The specification is provided above. Generate the JSON immediately.`;
Generate ${featureCount} features that build on each other logically.`;

logger.info('========== PROMPT BEING SENT ==========');
logger.info(`Prompt length: ${prompt.length} chars`);
Expand Down
18 changes: 7 additions & 11 deletions apps/server/src/routes/app-spec/generate-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,7 @@

import * as secureFs from '../../lib/secure-fs.js';
import type { EventEmitter } from '../../lib/events.js';
import {
specOutputSchema,
specToXml,
getStructuredSpecPromptInstruction,
type SpecOutput,
} from '../../lib/app-spec-format.js';
import { specOutputSchema, specToXml, type SpecOutput } from '../../lib/app-spec-format.js';
import { createLogger } from '@automaker/utils';
import { DEFAULT_PHASE_MODELS, isCursorModel } from '@automaker/types';
import { resolvePhaseModel } from '@automaker/model-resolver';
Expand All @@ -21,7 +16,7 @@ import { streamingQuery } from '../../providers/simple-query-service.js';
import { generateFeaturesFromSpec } from './generate-features-from-spec.js';
import { ensureAutomakerDir, getAppSpecPath } from '@automaker/platform';
import type { SettingsService } from '../../services/settings-service.js';
import { getAutoLoadClaudeMdSetting } from '../../lib/settings-helpers.js';
import { getAutoLoadClaudeMdSetting, getPromptCustomization } from '../../lib/settings-helpers.js';

const logger = createLogger('SpecRegeneration');

Expand All @@ -43,6 +38,9 @@ export async function generateSpec(
logger.info('analyzeProject:', analyzeProject);
logger.info('maxFeatures:', maxFeatures);

// Get customized prompts from settings
const prompts = await getPromptCustomization(settingsService, '[SpecRegeneration]');

// Build the prompt based on whether we should analyze the project
let analysisInstructions = '';
let techStackDefaults = '';
Expand All @@ -66,9 +64,7 @@ export async function generateSpec(
Use these technologies as the foundation for the specification.`;
}

const prompt = `You are helping to define a software project specification.

IMPORTANT: Never ask for clarification or additional information. Use the information provided and make reasonable assumptions to create the best possible specification. If details are missing, infer them based on common patterns and best practices.
const prompt = `${prompts.appSpec.generateSpecSystemPrompt}

Project Overview:
${projectOverview}
Expand All @@ -77,7 +73,7 @@ ${techStackDefaults}

${analysisInstructions}

${getStructuredSpecPromptInstruction()}`;
${prompts.appSpec.structuredSpecInstructions}`;

logger.info('========== PROMPT BEING SENT ==========');
logger.info(`Prompt length: ${prompt.length} chars`);
Expand Down
12 changes: 8 additions & 4 deletions apps/server/src/routes/context/routes/describe-file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ import { simpleQuery } from '../../../providers/simple-query-service.js';
import * as secureFs from '../../../lib/secure-fs.js';
import * as path from 'path';
import type { SettingsService } from '../../../services/settings-service.js';
import { getAutoLoadClaudeMdSetting } from '../../../lib/settings-helpers.js';
import {
getAutoLoadClaudeMdSetting,
getPromptCustomization,
} from '../../../lib/settings-helpers.js';

const logger = createLogger('DescribeFile');

Expand Down Expand Up @@ -130,11 +133,12 @@ export function createDescribeFileHandler(
// Get the filename for context
const fileName = path.basename(resolvedPath);

// Get customized prompts from settings
const prompts = await getPromptCustomization(settingsService, '[DescribeFile]');

// Build prompt with file content passed as structured data
// The file content is included directly, not via tool invocation
const prompt = `Analyze the following file and provide a 1-2 sentence description suitable for use as context in an AI coding assistant. Focus on what the file contains, its purpose, and why an AI agent might want to use this context in the future (e.g., "API documentation for the authentication endpoints", "Configuration file for database connections", "Coding style guidelines for the project").

Respond with ONLY the description text, no additional formatting, preamble, or explanation.
const prompt = `${prompts.contextDescription.describeFilePrompt}

File: ${fileName}${truncated ? ' (truncated)' : ''}

Expand Down
16 changes: 9 additions & 7 deletions apps/server/src/routes/context/routes/describe-image.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ import { simpleQuery } from '../../../providers/simple-query-service.js';
import * as secureFs from '../../../lib/secure-fs.js';
import * as path from 'path';
import type { SettingsService } from '../../../services/settings-service.js';
import { getAutoLoadClaudeMdSetting } from '../../../lib/settings-helpers.js';
import {
getAutoLoadClaudeMdSetting,
getPromptCustomization,
} from '../../../lib/settings-helpers.js';

const logger = createLogger('DescribeImage');

Expand Down Expand Up @@ -278,12 +281,11 @@ export function createDescribeImageHandler(

logger.info(`[${requestId}] Using model: ${model}`);

// Build the instruction text
const instructionText =
`Describe this image in 1-2 sentences suitable for use as context in an AI coding assistant. ` +
`Focus on what the image shows and its purpose (e.g., "UI mockup showing login form with email/password fields", ` +
`"Architecture diagram of microservices", "Screenshot of error message in terminal").\n\n` +
`Respond with ONLY the description text, no additional formatting, preamble, or explanation.`;
// Get customized prompts from settings
const prompts = await getPromptCustomization(settingsService, '[DescribeImage]');

// Build the instruction text from centralized prompts
const instructionText = prompts.contextDescription.describeImagePrompt;

// Build prompt based on provider capability
// Some providers (like Cursor) may not support image content blocks
Expand Down
8 changes: 6 additions & 2 deletions apps/server/src/routes/features/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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));
Expand All @@ -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;
}
21 changes: 10 additions & 11 deletions apps/server/src/routes/features/routes/generate-title.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');

Expand All @@ -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<void> {
export function createGenerateTitleHandler(
settingsService?: SettingsService
): (req: Request, res: Response) => Promise<void> {
return async (req: Request, res: Response): Promise<void> => {
try {
const { description } = req.body as GenerateTitleRequestBody;
Expand All @@ -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,
Expand Down
10 changes: 7 additions & 3 deletions apps/server/src/routes/github/routes/validate-issue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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,
Expand Down
Loading