Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
104 changes: 99 additions & 5 deletions apps/server/src/lib/settings-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,18 @@
import type { SettingsService } from '../services/settings-service.js';
import type { ContextFilesResult, ContextFileInfo } from '@automaker/utils';
import { createLogger } from '@automaker/utils';
import type { MCPServerConfig, McpServerConfig, PromptCustomization } from '@automaker/types';
import type {
MCPServerConfig,
McpServerConfig,
PromptCustomization,
LanguageInstruction,
} from '@automaker/types';
import {
mergeAutoModePrompts,
mergeAgentPrompts,
mergeBacklogPlanPrompts,
mergeEnhancementPrompts,
prependLanguageInstruction,
} from '@automaker/prompts';

const logger = createLogger('SettingsHelper');
Expand Down Expand Up @@ -205,6 +211,7 @@ function convertToSdkFormat(server: MCPServerConfig): McpServerConfig {
/**
* Get prompt customization from global settings and merge with defaults.
* Returns prompts merged with built-in defaults - custom prompts override defaults.
* Also applies language instruction to all system prompts if enabled.
*
* @param settingsService - Optional settings service instance
* @param logPrefix - Prefix for log messages
Expand All @@ -218,14 +225,20 @@ export async function getPromptCustomization(
agent: ReturnType<typeof mergeAgentPrompts>;
backlogPlan: ReturnType<typeof mergeBacklogPlanPrompts>;
enhancement: ReturnType<typeof mergeEnhancementPrompts>;
languageInstruction?: LanguageInstruction;
}> {
let customization: PromptCustomization = {};
let languageInstruction: LanguageInstruction | undefined;

if (settingsService) {
try {
const globalSettings = await settingsService.getGlobalSettings();
customization = globalSettings.promptCustomization || {};
languageInstruction = globalSettings.languageInstruction;
logger.info(`${logPrefix} Loaded prompt customization from settings`);
if (languageInstruction?.enabled) {
logger.info(`${logPrefix} Language instruction enabled: ${languageInstruction.language}`);
}
} catch (error) {
logger.error(`${logPrefix} Failed to load prompt customization:`, error);
// Fall through to use empty customization (all defaults)
Expand All @@ -234,14 +247,95 @@ export async function getPromptCustomization(
logger.info(`${logPrefix} SettingsService not available, using default prompts`);
}

// Merge prompts with defaults
const mergedAutoMode = mergeAutoModePrompts(customization.autoMode);
const mergedAgent = mergeAgentPrompts(customization.agent);
const mergedBacklogPlan = mergeBacklogPlanPrompts(customization.backlogPlan);
const mergedEnhancement = mergeEnhancementPrompts(customization.enhancement);

// Apply language instruction to system prompts if enabled
return {
autoMode: mergeAutoModePrompts(customization.autoMode),
agent: mergeAgentPrompts(customization.agent),
backlogPlan: mergeBacklogPlanPrompts(customization.backlogPlan),
enhancement: mergeEnhancementPrompts(customization.enhancement),
autoMode: {
planningLite: prependLanguageInstruction(mergedAutoMode.planningLite, languageInstruction),
planningLiteWithApproval: prependLanguageInstruction(
mergedAutoMode.planningLiteWithApproval,
languageInstruction
),
planningSpec: prependLanguageInstruction(mergedAutoMode.planningSpec, languageInstruction),
planningFull: prependLanguageInstruction(mergedAutoMode.planningFull, languageInstruction),
featurePromptTemplate: prependLanguageInstruction(
mergedAutoMode.featurePromptTemplate,
languageInstruction
),
followUpPromptTemplate: prependLanguageInstruction(
mergedAutoMode.followUpPromptTemplate,
languageInstruction
),
continuationPromptTemplate: prependLanguageInstruction(
mergedAutoMode.continuationPromptTemplate,
languageInstruction
),
pipelineStepPromptTemplate: prependLanguageInstruction(
mergedAutoMode.pipelineStepPromptTemplate,
languageInstruction
),
},
agent: {
systemPrompt: prependLanguageInstruction(mergedAgent.systemPrompt, languageInstruction),
},
backlogPlan: {
systemPrompt: prependLanguageInstruction(mergedBacklogPlan.systemPrompt, languageInstruction),
userPromptTemplate: mergedBacklogPlan.userPromptTemplate,
},
enhancement: {
improveSystemPrompt: prependLanguageInstruction(
mergedEnhancement.improveSystemPrompt,
languageInstruction
),
technicalSystemPrompt: prependLanguageInstruction(
mergedEnhancement.technicalSystemPrompt,
languageInstruction
),
simplifySystemPrompt: prependLanguageInstruction(
mergedEnhancement.simplifySystemPrompt,
languageInstruction
),
acceptanceSystemPrompt: prependLanguageInstruction(
mergedEnhancement.acceptanceSystemPrompt,
languageInstruction
),
uxReviewerSystemPrompt: prependLanguageInstruction(
mergedEnhancement.uxReviewerSystemPrompt,
languageInstruction
),
},
languageInstruction,
};
}

/**
* Get language instruction from global settings.
* Use this when you need to apply language instruction separately from prompt customization.
*
* @param settingsService - Optional settings service instance
* @returns Promise resolving to the language instruction or undefined if not set
*/
export async function getLanguageInstruction(
settingsService?: SettingsService | null
): Promise<LanguageInstruction | undefined> {
if (!settingsService) {
return undefined;
}

try {
const globalSettings = await settingsService.getGlobalSettings();
return globalSettings.languageInstruction;
} catch (error) {
logger.error('[SettingsHelper] Failed to load language instruction:', error);
return undefined;
}
}

/**
* Get Skills configuration from settings.
* Returns configuration for enabling skills and which sources to load from.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ 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, getLanguageInstruction } from '../../lib/settings-helpers.js';
import { prependLanguageInstruction } from '@automaker/prompts';

const logger = createLogger('SpecRegeneration');

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

const prompt = `Based on this project specification:
const basePrompt = `Based on this project specification:

${spec}

Expand Down Expand Up @@ -86,6 +87,10 @@ Generate ${featureCount} features that build on each other logically.

IMPORTANT: Do not ask for clarification. The specification is provided above. Generate the JSON immediately.`;

// Load language instruction from settings
const languageInstruction = await getLanguageInstruction(settingsService);
const prompt = prependLanguageInstruction(basePrompt, languageInstruction);

logger.info('========== PROMPT BEING SENT ==========');
logger.info(`Prompt length: ${prompt.length} chars`);
logger.info(`Prompt preview (first 1000 chars):\n${prompt.substring(0, 1000)}`);
Expand Down
9 changes: 7 additions & 2 deletions apps/server/src/routes/app-spec/generate-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ 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, getLanguageInstruction } from '../../lib/settings-helpers.js';
import { prependLanguageInstruction } from '@automaker/prompts';

const logger = createLogger('SpecRegeneration');

Expand Down Expand Up @@ -66,7 +67,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.
const basePrompt = `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.

Expand All @@ -79,6 +80,10 @@ ${analysisInstructions}

${getStructuredSpecPromptInstruction()}`;

// Load language instruction from settings
const languageInstruction = await getLanguageInstruction(settingsService);
const prompt = prependLanguageInstruction(basePrompt, languageInstruction);

logger.info('========== PROMPT BEING SENT ==========');
logger.info(`Prompt length: ${prompt.length} chars`);
logger.info(`Prompt preview (first 500 chars):\n${prompt.substring(0, 500)}`);
Expand Down
12 changes: 10 additions & 2 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,11 @@ 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,
getLanguageInstruction,
} from '../../../lib/settings-helpers.js';
import { prependLanguageInstruction } from '@automaker/prompts';

const logger = createLogger('DescribeFile');

Expand Down Expand Up @@ -132,7 +136,7 @@ export function createDescribeFileHandler(

// 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").
const basePrompt = `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.

Expand All @@ -141,6 +145,10 @@ File: ${fileName}${truncated ? ' (truncated)' : ''}
--- FILE CONTENT ---
${contentToAnalyze}`;

// Load language instruction from settings
const languageInstruction = await getLanguageInstruction(settingsService);
const prompt = prependLanguageInstruction(basePrompt, languageInstruction);

// Use the file's directory as the working directory
const cwd = path.dirname(resolvedPath);

Expand Down
12 changes: 10 additions & 2 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,11 @@ 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,
getLanguageInstruction,
} from '../../../lib/settings-helpers.js';
import { prependLanguageInstruction } from '@automaker/prompts';

const logger = createLogger('DescribeImage');

Expand Down Expand Up @@ -279,12 +283,16 @@ export function createDescribeImageHandler(
logger.info(`[${requestId}] Using model: ${model}`);

// Build the instruction text
const instructionText =
const baseInstructionText =
`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.`;

// Load language instruction from settings
const languageInstruction = await getLanguageInstruction(settingsService);
const instructionText = prependLanguageInstruction(baseInstructionText, languageInstruction);

// Build prompt based on provider capability
// Some providers (like Cursor) may not support image content blocks
let prompt: string | Array<{ type: string; text?: string; source?: object }>;
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;
}
13 changes: 11 additions & 2 deletions apps/server/src/routes/features/routes/generate-title.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@
import type { Request, Response } from 'express';
import { createLogger } from '@automaker/utils';
import { CLAUDE_MODEL_MAP } from '@automaker/model-resolver';
import { prependLanguageInstruction } from '@automaker/prompts';
import { simpleQuery } from '../../../providers/simple-query-service.js';
import type { SettingsService } from '../../../services/settings-service.js';
import { getLanguageInstruction } from '../../../lib/settings-helpers.js';

const logger = createLogger('GenerateTitle');

Expand All @@ -35,7 +38,9 @@ Rules:
- 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 +66,15 @@ export function createGenerateTitleHandler(): (req: Request, res: Response) => P

logger.info(`Generating title for description: ${trimmedDescription.substring(0, 50)}...`);

// Load language instruction from settings
const languageInstruction = await getLanguageInstruction(settingsService);
const effectiveSystemPrompt = prependLanguageInstruction(SYSTEM_PROMPT, languageInstruction);

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: `${effectiveSystemPrompt}\n\n${userPrompt}`,
model: CLAUDE_MODEL_MAP.haiku,
cwd: process.cwd(),
maxTurns: 1,
Expand Down
Loading