refactor(06-03): migrate Batch 4 complex routes to facade pattern

- run-feature.ts: Add facadeFactory parameter, use facade.checkWorktreeCapacity/executeFeature
- follow-up-feature.ts: Add facadeFactory parameter, use facade.followUpFeature
- approve-plan.ts: Add facadeFactory parameter, use facade.resolvePlanApproval
- analyze-project.ts: Add facadeFactory parameter, use facade.analyzeProject
- All routes maintain backward compatibility with autoModeService fallback
This commit is contained in:
Shirone
2026-01-30 21:39:45 +01:00
parent ffbfd2b79b
commit 5f9eacd01e
4 changed files with 118 additions and 4 deletions

View File

@@ -4,12 +4,16 @@
import type { Request, Response } from 'express';
import type { AutoModeService } from '../../../services/auto-mode-service.js';
import type { AutoModeServiceFacade } from '../../../services/auto-mode/index.js';
import { createLogger } from '@automaker/utils';
import { getErrorMessage, logError } from '../common.js';
const logger = createLogger('AutoMode');
export function createAnalyzeProjectHandler(autoModeService: AutoModeService) {
export function createAnalyzeProjectHandler(
autoModeService: AutoModeService,
facadeFactory?: (projectPath: string) => AutoModeServiceFacade
) {
return async (req: Request, res: Response): Promise<void> => {
try {
const { projectPath } = req.body as { projectPath: string };
@@ -19,6 +23,19 @@ export function createAnalyzeProjectHandler(autoModeService: AutoModeService) {
return;
}
// Use facade if factory is provided, otherwise fall back to autoModeService
if (facadeFactory) {
const facade = facadeFactory(projectPath);
// Start analysis in background
facade.analyzeProject().catch((error) => {
logger.error(`[AutoMode] Project analysis error:`, error);
});
res.json({ success: true, message: 'Project analysis started' });
return;
}
// Legacy path: use autoModeService directly
// Start analysis in background
autoModeService.analyzeProject(projectPath).catch((error) => {
logger.error(`[AutoMode] Project analysis error:`, error);

View File

@@ -4,12 +4,16 @@
import type { Request, Response } from 'express';
import type { AutoModeService } from '../../../services/auto-mode-service.js';
import type { AutoModeServiceFacade } from '../../../services/auto-mode/index.js';
import { createLogger } from '@automaker/utils';
import { getErrorMessage, logError } from '../common.js';
const logger = createLogger('AutoMode');
export function createApprovePlanHandler(autoModeService: AutoModeService) {
export function createApprovePlanHandler(
autoModeService: AutoModeService,
facadeFactory?: (projectPath: string) => AutoModeServiceFacade
) {
return async (req: Request, res: Response): Promise<void> => {
try {
const { featureId, approved, editedPlan, feedback, projectPath } = req.body as {
@@ -46,6 +50,30 @@ export function createApprovePlanHandler(autoModeService: AutoModeService) {
}${feedback ? ` - Feedback: ${feedback}` : ''}`
);
// Use facade if factory is provided and projectPath is available
if (facadeFactory && projectPath) {
const facade = facadeFactory(projectPath);
const result = await facade.resolvePlanApproval(featureId, approved, editedPlan, feedback);
if (!result.success) {
res.status(500).json({
success: false,
error: result.error,
});
return;
}
res.json({
success: true,
approved,
message: approved
? 'Plan approved - implementation will continue'
: 'Plan rejected - feature execution stopped',
});
return;
}
// Legacy path: use autoModeService directly
// Resolve the pending approval (with recovery support)
const result = await autoModeService.resolvePlanApproval(
featureId,

View File

@@ -4,12 +4,16 @@
import type { Request, Response } from 'express';
import type { AutoModeService } from '../../../services/auto-mode-service.js';
import type { AutoModeServiceFacade } from '../../../services/auto-mode/index.js';
import { createLogger } from '@automaker/utils';
import { getErrorMessage, logError } from '../common.js';
const logger = createLogger('AutoMode');
export function createFollowUpFeatureHandler(autoModeService: AutoModeService) {
export function createFollowUpFeatureHandler(
autoModeService: AutoModeService,
facadeFactory?: (projectPath: string) => AutoModeServiceFacade
) {
return async (req: Request, res: Response): Promise<void> => {
try {
const { projectPath, featureId, prompt, imagePaths, useWorktrees } = req.body as {
@@ -28,6 +32,28 @@ export function createFollowUpFeatureHandler(autoModeService: AutoModeService) {
return;
}
// Use facade if factory is provided, otherwise fall back to autoModeService
if (facadeFactory) {
const facade = facadeFactory(projectPath);
// Start follow-up in background
// followUpFeature derives workDir from feature.branchName
facade
// Default to false to match run-feature/resume-feature behavior.
// Worktrees should only be used when explicitly enabled by the user.
.followUpFeature(featureId, prompt, imagePaths, useWorktrees ?? false)
.catch((error) => {
logger.error(`[AutoMode] Follow up feature ${featureId} error:`, error);
})
.finally(() => {
// Release the starting slot when follow-up completes (success or error)
// Note: The feature should be in runningFeatures by this point
});
res.json({ success: true });
return;
}
// Legacy path: use autoModeService directly
// Start follow-up in background
// followUpFeature derives workDir from feature.branchName
autoModeService

View File

@@ -4,12 +4,16 @@
import type { Request, Response } from 'express';
import type { AutoModeService } from '../../../services/auto-mode-service.js';
import type { AutoModeServiceFacade } from '../../../services/auto-mode/index.js';
import { createLogger } from '@automaker/utils';
import { getErrorMessage, logError } from '../common.js';
const logger = createLogger('AutoMode');
export function createRunFeatureHandler(autoModeService: AutoModeService) {
export function createRunFeatureHandler(
autoModeService: AutoModeService,
facadeFactory?: (projectPath: string) => AutoModeServiceFacade
) {
return async (req: Request, res: Response): Promise<void> => {
try {
const { projectPath, featureId, useWorktrees } = req.body as {
@@ -26,6 +30,45 @@ export function createRunFeatureHandler(autoModeService: AutoModeService) {
return;
}
// Use facade if factory is provided, otherwise fall back to autoModeService
if (facadeFactory) {
const facade = facadeFactory(projectPath);
// Check per-worktree capacity before starting
const capacity = await facade.checkWorktreeCapacity(featureId);
if (!capacity.hasCapacity) {
const worktreeDesc = capacity.branchName
? `worktree "${capacity.branchName}"`
: 'main worktree';
res.status(429).json({
success: false,
error: `Agent limit reached for ${worktreeDesc} (${capacity.currentAgents}/${capacity.maxAgents}). Wait for running tasks to complete or increase the limit.`,
details: {
currentAgents: capacity.currentAgents,
maxAgents: capacity.maxAgents,
branchName: capacity.branchName,
},
});
return;
}
// Start execution in background
// executeFeature derives workDir from feature.branchName
facade
.executeFeature(featureId, useWorktrees ?? false, false)
.catch((error) => {
logger.error(`Feature ${featureId} error:`, error);
})
.finally(() => {
// Release the starting slot when execution completes (success or error)
// Note: The feature should be in runningFeatures by this point
});
res.json({ success: true });
return;
}
// Legacy path: use autoModeService directly
// Check per-worktree capacity before starting
const capacity = await autoModeService.checkWorktreeCapacity(projectPath, featureId);
if (!capacity.hasCapacity) {