mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-04 09:13:08 +00:00
feat: integrate settings service for auto-load CLAUDE.md functionality
- Updated API routes to accept an optional settings service for loading the autoLoadClaudeMd setting. - Introduced a new settings helper utility for retrieving project-specific settings. - Enhanced feature generation and spec generation processes to utilize the autoLoadClaudeMd setting. - Refactored relevant route handlers to support the new settings integration across various endpoints.
This commit is contained in:
@@ -149,17 +149,17 @@ app.use('/api/enhance-prompt', createEnhancePromptRoutes());
|
|||||||
app.use('/api/worktree', createWorktreeRoutes());
|
app.use('/api/worktree', createWorktreeRoutes());
|
||||||
app.use('/api/git', createGitRoutes());
|
app.use('/api/git', createGitRoutes());
|
||||||
app.use('/api/setup', createSetupRoutes());
|
app.use('/api/setup', createSetupRoutes());
|
||||||
app.use('/api/suggestions', createSuggestionsRoutes(events));
|
app.use('/api/suggestions', createSuggestionsRoutes(events, settingsService));
|
||||||
app.use('/api/models', createModelsRoutes());
|
app.use('/api/models', createModelsRoutes());
|
||||||
app.use('/api/spec-regeneration', createSpecRegenerationRoutes(events));
|
app.use('/api/spec-regeneration', createSpecRegenerationRoutes(events, settingsService));
|
||||||
app.use('/api/running-agents', createRunningAgentsRoutes(autoModeService));
|
app.use('/api/running-agents', createRunningAgentsRoutes(autoModeService));
|
||||||
app.use('/api/workspace', createWorkspaceRoutes());
|
app.use('/api/workspace', createWorkspaceRoutes());
|
||||||
app.use('/api/templates', createTemplatesRoutes());
|
app.use('/api/templates', createTemplatesRoutes());
|
||||||
app.use('/api/terminal', createTerminalRoutes());
|
app.use('/api/terminal', createTerminalRoutes());
|
||||||
app.use('/api/settings', createSettingsRoutes(settingsService));
|
app.use('/api/settings', createSettingsRoutes(settingsService));
|
||||||
app.use('/api/claude', createClaudeRoutes(claudeUsageService));
|
app.use('/api/claude', createClaudeRoutes(claudeUsageService));
|
||||||
app.use('/api/github', createGitHubRoutes(events));
|
app.use('/api/github', createGitHubRoutes(events, settingsService));
|
||||||
app.use('/api/context', createContextRoutes());
|
app.use('/api/context', createContextRoutes(settingsService));
|
||||||
|
|
||||||
// Create HTTP server
|
// Create HTTP server
|
||||||
const server = createServer(app);
|
const server = createServer(app);
|
||||||
|
|||||||
45
apps/server/src/lib/settings-helpers.ts
Normal file
45
apps/server/src/lib/settings-helpers.ts
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
/**
|
||||||
|
* Helper utilities for loading settings across different parts of the server
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { SettingsService } from '../services/settings-service.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the autoLoadClaudeMd setting, with project settings taking precedence over global.
|
||||||
|
* Returns false if settings service is not available.
|
||||||
|
*
|
||||||
|
* @param projectPath - Path to the project
|
||||||
|
* @param settingsService - Optional settings service instance
|
||||||
|
* @param logPrefix - Prefix for log messages (e.g., '[DescribeImage]')
|
||||||
|
* @returns Promise resolving to the autoLoadClaudeMd setting value
|
||||||
|
*/
|
||||||
|
export async function getAutoLoadClaudeMdSetting(
|
||||||
|
projectPath: string,
|
||||||
|
settingsService?: SettingsService,
|
||||||
|
logPrefix = '[SettingsHelper]'
|
||||||
|
): Promise<boolean> {
|
||||||
|
if (!settingsService) {
|
||||||
|
console.log(`${logPrefix} SettingsService not available, autoLoadClaudeMd disabled`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Check project settings first (takes precedence)
|
||||||
|
const projectSettings = await settingsService.getProjectSettings(projectPath);
|
||||||
|
if (projectSettings.autoLoadClaudeMd !== undefined) {
|
||||||
|
console.log(
|
||||||
|
`${logPrefix} autoLoadClaudeMd from project settings: ${projectSettings.autoLoadClaudeMd}`
|
||||||
|
);
|
||||||
|
return projectSettings.autoLoadClaudeMd;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back to global settings
|
||||||
|
const globalSettings = await settingsService.getGlobalSettings();
|
||||||
|
const result = globalSettings.autoLoadClaudeMd ?? false;
|
||||||
|
console.log(`${logPrefix} autoLoadClaudeMd from global settings: ${result}`);
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`${logPrefix} Failed to load autoLoadClaudeMd setting:`, error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,8 @@ import { createFeatureGenerationOptions } from '../../lib/sdk-options.js';
|
|||||||
import { logAuthStatus } from './common.js';
|
import { logAuthStatus } from './common.js';
|
||||||
import { parseAndCreateFeatures } from './parse-and-create-features.js';
|
import { parseAndCreateFeatures } from './parse-and-create-features.js';
|
||||||
import { getAppSpecPath } from '@automaker/platform';
|
import { getAppSpecPath } from '@automaker/platform';
|
||||||
|
import type { SettingsService } from '../../services/settings-service.js';
|
||||||
|
import { getAutoLoadClaudeMdSetting } from '../../lib/settings-helpers.js';
|
||||||
|
|
||||||
const logger = createLogger('SpecRegeneration');
|
const logger = createLogger('SpecRegeneration');
|
||||||
|
|
||||||
@@ -19,7 +21,8 @@ export async function generateFeaturesFromSpec(
|
|||||||
projectPath: string,
|
projectPath: string,
|
||||||
events: EventEmitter,
|
events: EventEmitter,
|
||||||
abortController: AbortController,
|
abortController: AbortController,
|
||||||
maxFeatures?: number
|
maxFeatures?: number,
|
||||||
|
settingsService?: SettingsService
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const featureCount = maxFeatures ?? DEFAULT_MAX_FEATURES;
|
const featureCount = maxFeatures ?? DEFAULT_MAX_FEATURES;
|
||||||
logger.debug('========== generateFeaturesFromSpec() started ==========');
|
logger.debug('========== generateFeaturesFromSpec() started ==========');
|
||||||
@@ -91,9 +94,17 @@ IMPORTANT: Do not ask for clarification. The specification is provided above. Ge
|
|||||||
projectPath: projectPath,
|
projectPath: projectPath,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Load autoLoadClaudeMd setting
|
||||||
|
const autoLoadClaudeMd = await getAutoLoadClaudeMdSetting(
|
||||||
|
projectPath,
|
||||||
|
settingsService,
|
||||||
|
'[FeatureGeneration]'
|
||||||
|
);
|
||||||
|
|
||||||
const options = createFeatureGenerationOptions({
|
const options = createFeatureGenerationOptions({
|
||||||
cwd: projectPath,
|
cwd: projectPath,
|
||||||
abortController,
|
abortController,
|
||||||
|
autoLoadClaudeMd,
|
||||||
});
|
});
|
||||||
|
|
||||||
logger.debug('SDK Options:', JSON.stringify(options, null, 2));
|
logger.debug('SDK Options:', JSON.stringify(options, null, 2));
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ import { createSpecGenerationOptions } from '../../lib/sdk-options.js';
|
|||||||
import { logAuthStatus } from './common.js';
|
import { logAuthStatus } from './common.js';
|
||||||
import { generateFeaturesFromSpec } from './generate-features-from-spec.js';
|
import { generateFeaturesFromSpec } from './generate-features-from-spec.js';
|
||||||
import { ensureAutomakerDir, getAppSpecPath } from '@automaker/platform';
|
import { ensureAutomakerDir, getAppSpecPath } from '@automaker/platform';
|
||||||
|
import type { SettingsService } from '../../services/settings-service.js';
|
||||||
|
import { getAutoLoadClaudeMdSetting } from '../../lib/settings-helpers.js';
|
||||||
|
|
||||||
const logger = createLogger('SpecRegeneration');
|
const logger = createLogger('SpecRegeneration');
|
||||||
|
|
||||||
@@ -27,7 +29,8 @@ export async function generateSpec(
|
|||||||
abortController: AbortController,
|
abortController: AbortController,
|
||||||
generateFeatures?: boolean,
|
generateFeatures?: boolean,
|
||||||
analyzeProject?: boolean,
|
analyzeProject?: boolean,
|
||||||
maxFeatures?: number
|
maxFeatures?: number,
|
||||||
|
settingsService?: SettingsService
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
logger.info('========== generateSpec() started ==========');
|
logger.info('========== generateSpec() started ==========');
|
||||||
logger.info('projectPath:', projectPath);
|
logger.info('projectPath:', projectPath);
|
||||||
@@ -83,9 +86,17 @@ ${getStructuredSpecPromptInstruction()}`;
|
|||||||
content: 'Starting spec generation...\n',
|
content: 'Starting spec generation...\n',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Load autoLoadClaudeMd setting
|
||||||
|
const autoLoadClaudeMd = await getAutoLoadClaudeMdSetting(
|
||||||
|
projectPath,
|
||||||
|
settingsService,
|
||||||
|
'[SpecRegeneration]'
|
||||||
|
);
|
||||||
|
|
||||||
const options = createSpecGenerationOptions({
|
const options = createSpecGenerationOptions({
|
||||||
cwd: projectPath,
|
cwd: projectPath,
|
||||||
abortController,
|
abortController,
|
||||||
|
autoLoadClaudeMd,
|
||||||
outputFormat: {
|
outputFormat: {
|
||||||
type: 'json_schema',
|
type: 'json_schema',
|
||||||
schema: specOutputSchema,
|
schema: specOutputSchema,
|
||||||
@@ -269,7 +280,13 @@ ${getStructuredSpecPromptInstruction()}`;
|
|||||||
// Create a new abort controller for feature generation
|
// Create a new abort controller for feature generation
|
||||||
const featureAbortController = new AbortController();
|
const featureAbortController = new AbortController();
|
||||||
try {
|
try {
|
||||||
await generateFeaturesFromSpec(projectPath, events, featureAbortController, maxFeatures);
|
await generateFeaturesFromSpec(
|
||||||
|
projectPath,
|
||||||
|
events,
|
||||||
|
featureAbortController,
|
||||||
|
maxFeatures,
|
||||||
|
settingsService
|
||||||
|
);
|
||||||
// Final completion will be emitted by generateFeaturesFromSpec -> parseAndCreateFeatures
|
// Final completion will be emitted by generateFeaturesFromSpec -> parseAndCreateFeatures
|
||||||
} catch (featureError) {
|
} catch (featureError) {
|
||||||
logger.error('Feature generation failed:', featureError);
|
logger.error('Feature generation failed:', featureError);
|
||||||
|
|||||||
@@ -9,13 +9,17 @@ import { createGenerateHandler } from './routes/generate.js';
|
|||||||
import { createGenerateFeaturesHandler } from './routes/generate-features.js';
|
import { createGenerateFeaturesHandler } from './routes/generate-features.js';
|
||||||
import { createStopHandler } from './routes/stop.js';
|
import { createStopHandler } from './routes/stop.js';
|
||||||
import { createStatusHandler } from './routes/status.js';
|
import { createStatusHandler } from './routes/status.js';
|
||||||
|
import type { SettingsService } from '../../services/settings-service.js';
|
||||||
|
|
||||||
export function createSpecRegenerationRoutes(events: EventEmitter): Router {
|
export function createSpecRegenerationRoutes(
|
||||||
|
events: EventEmitter,
|
||||||
|
settingsService?: SettingsService
|
||||||
|
): Router {
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.post('/create', createCreateHandler(events));
|
router.post('/create', createCreateHandler(events));
|
||||||
router.post('/generate', createGenerateHandler(events));
|
router.post('/generate', createGenerateHandler(events, settingsService));
|
||||||
router.post('/generate-features', createGenerateFeaturesHandler(events));
|
router.post('/generate-features', createGenerateFeaturesHandler(events, settingsService));
|
||||||
router.post('/stop', createStopHandler());
|
router.post('/stop', createStopHandler());
|
||||||
router.get('/status', createStatusHandler());
|
router.get('/status', createStatusHandler());
|
||||||
|
|
||||||
|
|||||||
@@ -13,10 +13,14 @@ import {
|
|||||||
getErrorMessage,
|
getErrorMessage,
|
||||||
} from '../common.js';
|
} from '../common.js';
|
||||||
import { generateFeaturesFromSpec } from '../generate-features-from-spec.js';
|
import { generateFeaturesFromSpec } from '../generate-features-from-spec.js';
|
||||||
|
import type { SettingsService } from '../../../services/settings-service.js';
|
||||||
|
|
||||||
const logger = createLogger('SpecRegeneration');
|
const logger = createLogger('SpecRegeneration');
|
||||||
|
|
||||||
export function createGenerateFeaturesHandler(events: EventEmitter) {
|
export function createGenerateFeaturesHandler(
|
||||||
|
events: EventEmitter,
|
||||||
|
settingsService?: SettingsService
|
||||||
|
) {
|
||||||
return async (req: Request, res: Response): Promise<void> => {
|
return async (req: Request, res: Response): Promise<void> => {
|
||||||
logger.info('========== /generate-features endpoint called ==========');
|
logger.info('========== /generate-features endpoint called ==========');
|
||||||
logger.debug('Request body:', JSON.stringify(req.body, null, 2));
|
logger.debug('Request body:', JSON.stringify(req.body, null, 2));
|
||||||
@@ -49,7 +53,7 @@ export function createGenerateFeaturesHandler(events: EventEmitter) {
|
|||||||
setRunningState(true, abortController);
|
setRunningState(true, abortController);
|
||||||
logger.info('Starting background feature generation task...');
|
logger.info('Starting background feature generation task...');
|
||||||
|
|
||||||
generateFeaturesFromSpec(projectPath, events, abortController, maxFeatures)
|
generateFeaturesFromSpec(projectPath, events, abortController, maxFeatures, settingsService)
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
logError(error, 'Feature generation failed with error');
|
logError(error, 'Feature generation failed with error');
|
||||||
events.emit('spec-regeneration:event', {
|
events.emit('spec-regeneration:event', {
|
||||||
|
|||||||
@@ -13,10 +13,11 @@ import {
|
|||||||
getErrorMessage,
|
getErrorMessage,
|
||||||
} from '../common.js';
|
} from '../common.js';
|
||||||
import { generateSpec } from '../generate-spec.js';
|
import { generateSpec } from '../generate-spec.js';
|
||||||
|
import type { SettingsService } from '../../../services/settings-service.js';
|
||||||
|
|
||||||
const logger = createLogger('SpecRegeneration');
|
const logger = createLogger('SpecRegeneration');
|
||||||
|
|
||||||
export function createGenerateHandler(events: EventEmitter) {
|
export function createGenerateHandler(events: EventEmitter, settingsService?: SettingsService) {
|
||||||
return async (req: Request, res: Response): Promise<void> => {
|
return async (req: Request, res: Response): Promise<void> => {
|
||||||
logger.info('========== /generate endpoint called ==========');
|
logger.info('========== /generate endpoint called ==========');
|
||||||
logger.debug('Request body:', JSON.stringify(req.body, null, 2));
|
logger.debug('Request body:', JSON.stringify(req.body, null, 2));
|
||||||
@@ -67,7 +68,8 @@ export function createGenerateHandler(events: EventEmitter) {
|
|||||||
abortController,
|
abortController,
|
||||||
generateFeatures,
|
generateFeatures,
|
||||||
analyzeProject,
|
analyzeProject,
|
||||||
maxFeatures
|
maxFeatures,
|
||||||
|
settingsService
|
||||||
)
|
)
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
logError(error, 'Generation failed with error');
|
logError(error, 'Generation failed with error');
|
||||||
|
|||||||
@@ -8,17 +8,19 @@
|
|||||||
import { Router } from 'express';
|
import { Router } from 'express';
|
||||||
import { createDescribeImageHandler } from './routes/describe-image.js';
|
import { createDescribeImageHandler } from './routes/describe-image.js';
|
||||||
import { createDescribeFileHandler } from './routes/describe-file.js';
|
import { createDescribeFileHandler } from './routes/describe-file.js';
|
||||||
|
import type { SettingsService } from '../../services/settings-service.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create the context router
|
* Create the context router
|
||||||
*
|
*
|
||||||
|
* @param settingsService - Optional settings service for loading autoLoadClaudeMd setting
|
||||||
* @returns Express router with context endpoints
|
* @returns Express router with context endpoints
|
||||||
*/
|
*/
|
||||||
export function createContextRoutes(): Router {
|
export function createContextRoutes(settingsService?: SettingsService): Router {
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.post('/describe-image', createDescribeImageHandler());
|
router.post('/describe-image', createDescribeImageHandler(settingsService));
|
||||||
router.post('/describe-file', createDescribeFileHandler());
|
router.post('/describe-file', createDescribeFileHandler(settingsService));
|
||||||
|
|
||||||
return router;
|
return router;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ import { PathNotAllowedError } from '@automaker/platform';
|
|||||||
import { createCustomOptions } from '../../../lib/sdk-options.js';
|
import { createCustomOptions } from '../../../lib/sdk-options.js';
|
||||||
import * as secureFs from '../../../lib/secure-fs.js';
|
import * as secureFs from '../../../lib/secure-fs.js';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
import type { SettingsService } from '../../../services/settings-service.js';
|
||||||
|
import { getAutoLoadClaudeMdSetting } from '../../../lib/settings-helpers.js';
|
||||||
|
|
||||||
const logger = createLogger('DescribeFile');
|
const logger = createLogger('DescribeFile');
|
||||||
|
|
||||||
@@ -72,9 +74,12 @@ async function extractTextFromStream(
|
|||||||
/**
|
/**
|
||||||
* Create the describe-file request handler
|
* Create the describe-file request handler
|
||||||
*
|
*
|
||||||
|
* @param settingsService - Optional settings service for loading autoLoadClaudeMd setting
|
||||||
* @returns Express request handler for file description
|
* @returns Express request handler for file description
|
||||||
*/
|
*/
|
||||||
export function createDescribeFileHandler(): (req: Request, res: Response) => Promise<void> {
|
export function createDescribeFileHandler(
|
||||||
|
settingsService?: SettingsService
|
||||||
|
): (req: Request, res: Response) => Promise<void> {
|
||||||
return async (req: Request, res: Response): Promise<void> => {
|
return async (req: Request, res: Response): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
const { filePath } = req.body as DescribeFileRequestBody;
|
const { filePath } = req.body as DescribeFileRequestBody;
|
||||||
@@ -165,6 +170,13 @@ File: ${fileName}${truncated ? ' (truncated)' : ''}`;
|
|||||||
// Use the file's directory as the working directory
|
// Use the file's directory as the working directory
|
||||||
const cwd = path.dirname(resolvedPath);
|
const cwd = path.dirname(resolvedPath);
|
||||||
|
|
||||||
|
// Load autoLoadClaudeMd setting
|
||||||
|
const autoLoadClaudeMd = await getAutoLoadClaudeMdSetting(
|
||||||
|
cwd,
|
||||||
|
settingsService,
|
||||||
|
'[DescribeFile]'
|
||||||
|
);
|
||||||
|
|
||||||
// Use centralized SDK options with proper cwd validation
|
// Use centralized SDK options with proper cwd validation
|
||||||
// No tools needed since we're passing file content directly
|
// No tools needed since we're passing file content directly
|
||||||
const sdkOptions = createCustomOptions({
|
const sdkOptions = createCustomOptions({
|
||||||
@@ -172,6 +184,7 @@ File: ${fileName}${truncated ? ' (truncated)' : ''}`;
|
|||||||
model: CLAUDE_MODEL_MAP.haiku,
|
model: CLAUDE_MODEL_MAP.haiku,
|
||||||
maxTurns: 1,
|
maxTurns: 1,
|
||||||
allowedTools: [],
|
allowedTools: [],
|
||||||
|
autoLoadClaudeMd,
|
||||||
sandbox: { enabled: true, autoAllowBashIfSandboxed: true },
|
sandbox: { enabled: true, autoAllowBashIfSandboxed: true },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ import { CLAUDE_MODEL_MAP } from '@automaker/types';
|
|||||||
import { createCustomOptions } from '../../../lib/sdk-options.js';
|
import { createCustomOptions } from '../../../lib/sdk-options.js';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
import type { SettingsService } from '../../../services/settings-service.js';
|
||||||
|
import { getAutoLoadClaudeMdSetting } from '../../../lib/settings-helpers.js';
|
||||||
|
|
||||||
const logger = createLogger('DescribeImage');
|
const logger = createLogger('DescribeImage');
|
||||||
|
|
||||||
@@ -226,9 +228,12 @@ async function extractTextFromStream(
|
|||||||
* Uses Claude SDK query with multi-part content blocks to include the image (base64),
|
* Uses Claude SDK query with multi-part content blocks to include the image (base64),
|
||||||
* matching the agent runner behavior.
|
* matching the agent runner behavior.
|
||||||
*
|
*
|
||||||
|
* @param settingsService - Optional settings service for loading autoLoadClaudeMd setting
|
||||||
* @returns Express request handler for image description
|
* @returns Express request handler for image description
|
||||||
*/
|
*/
|
||||||
export function createDescribeImageHandler(): (req: Request, res: Response) => Promise<void> {
|
export function createDescribeImageHandler(
|
||||||
|
settingsService?: SettingsService
|
||||||
|
): (req: Request, res: Response) => Promise<void> {
|
||||||
return async (req: Request, res: Response): Promise<void> => {
|
return async (req: Request, res: Response): Promise<void> => {
|
||||||
const requestId = `describe-image-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
const requestId = `describe-image-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
||||||
const startedAt = Date.now();
|
const startedAt = Date.now();
|
||||||
@@ -325,12 +330,20 @@ export function createDescribeImageHandler(): (req: Request, res: Response) => P
|
|||||||
const cwd = path.dirname(actualPath);
|
const cwd = path.dirname(actualPath);
|
||||||
logger.info(`[${requestId}] Using cwd=${cwd}`);
|
logger.info(`[${requestId}] Using cwd=${cwd}`);
|
||||||
|
|
||||||
|
// Load autoLoadClaudeMd setting
|
||||||
|
const autoLoadClaudeMd = await getAutoLoadClaudeMdSetting(
|
||||||
|
cwd,
|
||||||
|
settingsService,
|
||||||
|
'[DescribeImage]'
|
||||||
|
);
|
||||||
|
|
||||||
// Use the same centralized option builder used across the server (validates cwd)
|
// Use the same centralized option builder used across the server (validates cwd)
|
||||||
const sdkOptions = createCustomOptions({
|
const sdkOptions = createCustomOptions({
|
||||||
cwd,
|
cwd,
|
||||||
model: CLAUDE_MODEL_MAP.haiku,
|
model: CLAUDE_MODEL_MAP.haiku,
|
||||||
maxTurns: 1,
|
maxTurns: 1,
|
||||||
allowedTools: [],
|
allowedTools: [],
|
||||||
|
autoLoadClaudeMd,
|
||||||
sandbox: { enabled: true, autoAllowBashIfSandboxed: true },
|
sandbox: { enabled: true, autoAllowBashIfSandboxed: true },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -16,8 +16,12 @@ import {
|
|||||||
createDeleteValidationHandler,
|
createDeleteValidationHandler,
|
||||||
createMarkViewedHandler,
|
createMarkViewedHandler,
|
||||||
} from './routes/validation-endpoints.js';
|
} from './routes/validation-endpoints.js';
|
||||||
|
import type { SettingsService } from '../../services/settings-service.js';
|
||||||
|
|
||||||
export function createGitHubRoutes(events: EventEmitter): Router {
|
export function createGitHubRoutes(
|
||||||
|
events: EventEmitter,
|
||||||
|
settingsService?: SettingsService
|
||||||
|
): Router {
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.post('/check-remote', validatePathParams('projectPath'), createCheckGitHubRemoteHandler());
|
router.post('/check-remote', validatePathParams('projectPath'), createCheckGitHubRemoteHandler());
|
||||||
@@ -26,7 +30,7 @@ export function createGitHubRoutes(events: EventEmitter): Router {
|
|||||||
router.post(
|
router.post(
|
||||||
'/validate-issue',
|
'/validate-issue',
|
||||||
validatePathParams('projectPath'),
|
validatePathParams('projectPath'),
|
||||||
createValidateIssueHandler(events)
|
createValidateIssueHandler(events, settingsService)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Validation management endpoints
|
// Validation management endpoints
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ import {
|
|||||||
logError,
|
logError,
|
||||||
logger,
|
logger,
|
||||||
} from './validation-common.js';
|
} from './validation-common.js';
|
||||||
|
import type { SettingsService } from '../../../services/settings-service.js';
|
||||||
|
import { getAutoLoadClaudeMdSetting } from '../../../lib/settings-helpers.js';
|
||||||
|
|
||||||
/** Valid model values for validation */
|
/** Valid model values for validation */
|
||||||
const VALID_MODELS: readonly AgentModel[] = ['opus', 'sonnet', 'haiku'] as const;
|
const VALID_MODELS: readonly AgentModel[] = ['opus', 'sonnet', 'haiku'] as const;
|
||||||
@@ -54,7 +56,8 @@ async function runValidation(
|
|||||||
issueLabels: string[] | undefined,
|
issueLabels: string[] | undefined,
|
||||||
model: AgentModel,
|
model: AgentModel,
|
||||||
events: EventEmitter,
|
events: EventEmitter,
|
||||||
abortController: AbortController
|
abortController: AbortController,
|
||||||
|
settingsService?: SettingsService
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
// Emit start event
|
// Emit start event
|
||||||
const startEvent: IssueValidationEvent = {
|
const startEvent: IssueValidationEvent = {
|
||||||
@@ -76,12 +79,20 @@ async function runValidation(
|
|||||||
// Build the prompt
|
// Build the prompt
|
||||||
const prompt = buildValidationPrompt(issueNumber, issueTitle, issueBody, issueLabels);
|
const prompt = buildValidationPrompt(issueNumber, issueTitle, issueBody, issueLabels);
|
||||||
|
|
||||||
|
// Load autoLoadClaudeMd setting
|
||||||
|
const autoLoadClaudeMd = await getAutoLoadClaudeMdSetting(
|
||||||
|
projectPath,
|
||||||
|
settingsService,
|
||||||
|
'[ValidateIssue]'
|
||||||
|
);
|
||||||
|
|
||||||
// Create SDK options with structured output and abort controller
|
// Create SDK options with structured output and abort controller
|
||||||
const options = createSuggestionsOptions({
|
const options = createSuggestionsOptions({
|
||||||
cwd: projectPath,
|
cwd: projectPath,
|
||||||
model,
|
model,
|
||||||
systemPrompt: ISSUE_VALIDATION_SYSTEM_PROMPT,
|
systemPrompt: ISSUE_VALIDATION_SYSTEM_PROMPT,
|
||||||
abortController,
|
abortController,
|
||||||
|
autoLoadClaudeMd,
|
||||||
outputFormat: {
|
outputFormat: {
|
||||||
type: 'json_schema',
|
type: 'json_schema',
|
||||||
schema: issueValidationSchema as Record<string, unknown>,
|
schema: issueValidationSchema as Record<string, unknown>,
|
||||||
@@ -190,7 +201,10 @@ async function runValidation(
|
|||||||
* - System prompt guiding the validation process
|
* - System prompt guiding the validation process
|
||||||
* - Async execution with event emission
|
* - Async execution with event emission
|
||||||
*/
|
*/
|
||||||
export function createValidateIssueHandler(events: EventEmitter) {
|
export function createValidateIssueHandler(
|
||||||
|
events: EventEmitter,
|
||||||
|
settingsService?: SettingsService
|
||||||
|
) {
|
||||||
return async (req: Request, res: Response): Promise<void> => {
|
return async (req: Request, res: Response): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
const {
|
const {
|
||||||
@@ -256,7 +270,8 @@ export function createValidateIssueHandler(events: EventEmitter) {
|
|||||||
issueLabels,
|
issueLabels,
|
||||||
model,
|
model,
|
||||||
events,
|
events,
|
||||||
abortController
|
abortController,
|
||||||
|
settingsService
|
||||||
)
|
)
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
// Error is already handled inside runValidation (event emitted)
|
// Error is already handled inside runValidation (event emitted)
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import { createSuggestionsOptions } from '../../lib/sdk-options.js';
|
|||||||
import { FeatureLoader } from '../../services/feature-loader.js';
|
import { FeatureLoader } from '../../services/feature-loader.js';
|
||||||
import { getAppSpecPath } from '@automaker/platform';
|
import { getAppSpecPath } from '@automaker/platform';
|
||||||
import * as secureFs from '../../lib/secure-fs.js';
|
import * as secureFs from '../../lib/secure-fs.js';
|
||||||
|
import type { SettingsService } from '../../services/settings-service.js';
|
||||||
|
import { getAutoLoadClaudeMdSetting } from '../../lib/settings-helpers.js';
|
||||||
|
|
||||||
const logger = createLogger('Suggestions');
|
const logger = createLogger('Suggestions');
|
||||||
|
|
||||||
@@ -125,7 +127,8 @@ export async function generateSuggestions(
|
|||||||
projectPath: string,
|
projectPath: string,
|
||||||
suggestionType: string,
|
suggestionType: string,
|
||||||
events: EventEmitter,
|
events: EventEmitter,
|
||||||
abortController: AbortController
|
abortController: AbortController,
|
||||||
|
settingsService?: SettingsService
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const typePrompts: Record<string, string> = {
|
const typePrompts: Record<string, string> = {
|
||||||
features: 'Analyze this project and suggest new features that would add value.',
|
features: 'Analyze this project and suggest new features that would add value.',
|
||||||
@@ -154,9 +157,17 @@ The response will be automatically formatted as structured JSON.`;
|
|||||||
// Don't send initial message - let the agent output speak for itself
|
// Don't send initial message - let the agent output speak for itself
|
||||||
// The first agent message will be captured as an info entry
|
// The first agent message will be captured as an info entry
|
||||||
|
|
||||||
|
// Load autoLoadClaudeMd setting
|
||||||
|
const autoLoadClaudeMd = await getAutoLoadClaudeMdSetting(
|
||||||
|
projectPath,
|
||||||
|
settingsService,
|
||||||
|
'[Suggestions]'
|
||||||
|
);
|
||||||
|
|
||||||
const options = createSuggestionsOptions({
|
const options = createSuggestionsOptions({
|
||||||
cwd: projectPath,
|
cwd: projectPath,
|
||||||
abortController,
|
abortController,
|
||||||
|
autoLoadClaudeMd,
|
||||||
outputFormat: {
|
outputFormat: {
|
||||||
type: 'json_schema',
|
type: 'json_schema',
|
||||||
schema: suggestionsSchema,
|
schema: suggestionsSchema,
|
||||||
|
|||||||
@@ -8,11 +8,19 @@ import { validatePathParams } from '../../middleware/validate-paths.js';
|
|||||||
import { createGenerateHandler } from './routes/generate.js';
|
import { createGenerateHandler } from './routes/generate.js';
|
||||||
import { createStopHandler } from './routes/stop.js';
|
import { createStopHandler } from './routes/stop.js';
|
||||||
import { createStatusHandler } from './routes/status.js';
|
import { createStatusHandler } from './routes/status.js';
|
||||||
|
import type { SettingsService } from '../../services/settings-service.js';
|
||||||
|
|
||||||
export function createSuggestionsRoutes(events: EventEmitter): Router {
|
export function createSuggestionsRoutes(
|
||||||
|
events: EventEmitter,
|
||||||
|
settingsService?: SettingsService
|
||||||
|
): Router {
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.post('/generate', validatePathParams('projectPath'), createGenerateHandler(events));
|
router.post(
|
||||||
|
'/generate',
|
||||||
|
validatePathParams('projectPath'),
|
||||||
|
createGenerateHandler(events, settingsService)
|
||||||
|
);
|
||||||
router.post('/stop', createStopHandler());
|
router.post('/stop', createStopHandler());
|
||||||
router.get('/status', createStatusHandler());
|
router.get('/status', createStatusHandler());
|
||||||
|
|
||||||
|
|||||||
@@ -7,10 +7,11 @@ import type { EventEmitter } from '../../../lib/events.js';
|
|||||||
import { createLogger } from '@automaker/utils';
|
import { createLogger } from '@automaker/utils';
|
||||||
import { getSuggestionsStatus, setRunningState, getErrorMessage, logError } from '../common.js';
|
import { getSuggestionsStatus, setRunningState, getErrorMessage, logError } from '../common.js';
|
||||||
import { generateSuggestions } from '../generate-suggestions.js';
|
import { generateSuggestions } from '../generate-suggestions.js';
|
||||||
|
import type { SettingsService } from '../../../services/settings-service.js';
|
||||||
|
|
||||||
const logger = createLogger('Suggestions');
|
const logger = createLogger('Suggestions');
|
||||||
|
|
||||||
export function createGenerateHandler(events: EventEmitter) {
|
export function createGenerateHandler(events: EventEmitter, settingsService?: SettingsService) {
|
||||||
return async (req: Request, res: Response): Promise<void> => {
|
return async (req: Request, res: Response): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
const { projectPath, suggestionType = 'features' } = req.body as {
|
const { projectPath, suggestionType = 'features' } = req.body as {
|
||||||
@@ -37,7 +38,7 @@ export function createGenerateHandler(events: EventEmitter) {
|
|||||||
setRunningState(true, abortController);
|
setRunningState(true, abortController);
|
||||||
|
|
||||||
// Start generation in background
|
// Start generation in background
|
||||||
generateSuggestions(projectPath, suggestionType, events, abortController)
|
generateSuggestions(projectPath, suggestionType, events, abortController, settingsService)
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
logError(error, 'Generate suggestions failed (background)');
|
logError(error, 'Generate suggestions failed (background)');
|
||||||
events.emit('suggestions:event', {
|
events.emit('suggestions:event', {
|
||||||
|
|||||||
@@ -25,7 +25,11 @@ import { promisify } from 'util';
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
import * as secureFs from '../lib/secure-fs.js';
|
import * as secureFs from '../lib/secure-fs.js';
|
||||||
import type { EventEmitter } from '../lib/events.js';
|
import type { EventEmitter } from '../lib/events.js';
|
||||||
import { createAutoModeOptions, validateWorkingDirectory } from '../lib/sdk-options.js';
|
import {
|
||||||
|
createAutoModeOptions,
|
||||||
|
createCustomOptions,
|
||||||
|
validateWorkingDirectory,
|
||||||
|
} from '../lib/sdk-options.js';
|
||||||
import { FeatureLoader } from './feature-loader.js';
|
import { FeatureLoader } from './feature-loader.js';
|
||||||
import type { SettingsService } from './settings-service.js';
|
import type { SettingsService } from './settings-service.js';
|
||||||
|
|
||||||
@@ -1068,11 +1072,6 @@ Address the follow-up instructions above. Review the previous work and make the
|
|||||||
* Analyze project to gather context
|
* Analyze project to gather context
|
||||||
*/
|
*/
|
||||||
async analyzeProject(projectPath: string): Promise<void> {
|
async analyzeProject(projectPath: string): Promise<void> {
|
||||||
// Validate project path before proceeding
|
|
||||||
// This is called here because analyzeProject builds ExecuteOptions directly
|
|
||||||
// without using a factory function from sdk-options.ts
|
|
||||||
validateWorkingDirectory(projectPath);
|
|
||||||
|
|
||||||
const abortController = new AbortController();
|
const abortController = new AbortController();
|
||||||
|
|
||||||
const analysisFeatureId = `analysis-${Date.now()}`;
|
const analysisFeatureId = `analysis-${Date.now()}`;
|
||||||
@@ -1100,13 +1099,27 @@ Format your response as a structured markdown document.`;
|
|||||||
const analysisModel = resolveModelString(undefined, DEFAULT_MODELS.claude);
|
const analysisModel = resolveModelString(undefined, DEFAULT_MODELS.claude);
|
||||||
const provider = ProviderFactory.getProviderForModel(analysisModel);
|
const provider = ProviderFactory.getProviderForModel(analysisModel);
|
||||||
|
|
||||||
const options: ExecuteOptions = {
|
// Load autoLoadClaudeMd setting
|
||||||
prompt,
|
const autoLoadClaudeMd = await this.getAutoLoadClaudeMdSetting(projectPath);
|
||||||
|
|
||||||
|
// Use createCustomOptions for centralized SDK configuration with CLAUDE.md support
|
||||||
|
const sdkOptions = createCustomOptions({
|
||||||
|
cwd: projectPath,
|
||||||
model: analysisModel,
|
model: analysisModel,
|
||||||
maxTurns: 5,
|
maxTurns: 5,
|
||||||
cwd: projectPath,
|
|
||||||
allowedTools: ['Read', 'Glob', 'Grep'],
|
allowedTools: ['Read', 'Glob', 'Grep'],
|
||||||
abortController,
|
abortController,
|
||||||
|
autoLoadClaudeMd,
|
||||||
|
});
|
||||||
|
|
||||||
|
const options: ExecuteOptions = {
|
||||||
|
prompt,
|
||||||
|
model: sdkOptions.model ?? analysisModel,
|
||||||
|
cwd: sdkOptions.cwd ?? projectPath,
|
||||||
|
maxTurns: sdkOptions.maxTurns,
|
||||||
|
allowedTools: sdkOptions.allowedTools as string[],
|
||||||
|
abortController,
|
||||||
|
settingSources: sdkOptions.settingSources,
|
||||||
};
|
};
|
||||||
|
|
||||||
const stream = provider.executeQuery(options);
|
const stream = provider.executeQuery(options);
|
||||||
|
|||||||
@@ -19,6 +19,15 @@ export interface ConversationMessage {
|
|||||||
content: string | Array<{ type: string; text?: string; source?: object }>;
|
content: string | Array<{ type: string; text?: string; source?: object }>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* System prompt preset configuration for CLAUDE.md auto-loading
|
||||||
|
*/
|
||||||
|
export interface SystemPromptPreset {
|
||||||
|
type: 'preset';
|
||||||
|
preset: 'claude_code';
|
||||||
|
append?: string;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Options for executing a query via a provider
|
* Options for executing a query via a provider
|
||||||
*/
|
*/
|
||||||
@@ -26,13 +35,14 @@ export interface ExecuteOptions {
|
|||||||
prompt: string | Array<{ type: string; text?: string; source?: object }>;
|
prompt: string | Array<{ type: string; text?: string; source?: object }>;
|
||||||
model: string;
|
model: string;
|
||||||
cwd: string;
|
cwd: string;
|
||||||
systemPrompt?: string;
|
systemPrompt?: string | SystemPromptPreset;
|
||||||
maxTurns?: number;
|
maxTurns?: number;
|
||||||
allowedTools?: string[];
|
allowedTools?: string[];
|
||||||
mcpServers?: Record<string, unknown>;
|
mcpServers?: Record<string, unknown>;
|
||||||
abortController?: AbortController;
|
abortController?: AbortController;
|
||||||
conversationHistory?: ConversationMessage[]; // Previous messages for context
|
conversationHistory?: ConversationMessage[]; // Previous messages for context
|
||||||
sdkSessionId?: string; // Claude SDK session ID for resuming conversations
|
sdkSessionId?: string; // Claude SDK session ID for resuming conversations
|
||||||
|
settingSources?: Array<'user' | 'project' | 'local'>; // Sources for CLAUDE.md loading
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user