refactor(06-03): migrate Batch 5 secondary routes and wire router index

- features/routes/list.ts: Add facadeFactory parameter, use facade.detectOrphanedFeatures
- projects/routes/overview.ts: Add facadeFactory parameter, use facade.getRunningAgents/getStatusForProject
- features/index.ts: Pass facadeFactory to list handler
- projects/index.ts: Pass facadeFactory to overview handler
- auto-mode/index.ts: Accept facadeFactory parameter and wire to all route handlers
- All routes maintain backward compatibility with autoModeService fallback
This commit is contained in:
Shirone
2026-01-30 21:46:05 +01:00
parent 5f9eacd01e
commit 50c0b154f4
5 changed files with 97 additions and 23 deletions

View File

@@ -1,11 +1,13 @@
/**
* Auto Mode routes - HTTP API for autonomous feature implementation
*
* Uses the AutoModeService for real feature execution with Claude Agent SDK
* Uses the AutoModeService for real feature execution with Claude Agent SDK.
* Supports optional facadeFactory for per-project facade creation during migration.
*/
import { Router } from 'express';
import type { AutoModeService } from '../../services/auto-mode-service.js';
import type { AutoModeServiceFacade } from '../../services/auto-mode/index.js';
import { validatePathParams } from '../../middleware/validate-paths.js';
import { createStopFeatureHandler } from './routes/stop-feature.js';
import { createStatusHandler } from './routes/status.js';
@@ -21,59 +23,82 @@ import { createCommitFeatureHandler } from './routes/commit-feature.js';
import { createApprovePlanHandler } from './routes/approve-plan.js';
import { createResumeInterruptedHandler } from './routes/resume-interrupted.js';
export function createAutoModeRoutes(autoModeService: AutoModeService): Router {
/**
* Create auto-mode routes with optional facade factory.
*
* @param autoModeService - The AutoModeService instance (for backward compatibility)
* @param facadeFactory - Optional factory for creating per-project facades
*/
export function createAutoModeRoutes(
autoModeService: AutoModeService,
facadeFactory?: (projectPath: string) => AutoModeServiceFacade
): Router {
const router = Router();
// Auto loop control routes
router.post('/start', validatePathParams('projectPath'), createStartHandler(autoModeService));
router.post('/stop', validatePathParams('projectPath'), createStopHandler(autoModeService));
router.post(
'/start',
validatePathParams('projectPath'),
createStartHandler(autoModeService, facadeFactory)
);
router.post(
'/stop',
validatePathParams('projectPath'),
createStopHandler(autoModeService, facadeFactory)
);
// Note: stop-feature doesn't have projectPath, so we pass undefined for facade.
// When we fully migrate, we can update stop-feature to use a different approach.
router.post('/stop-feature', createStopFeatureHandler(autoModeService));
router.post('/status', validatePathParams('projectPath?'), createStatusHandler(autoModeService));
router.post(
'/status',
validatePathParams('projectPath?'),
createStatusHandler(autoModeService, facadeFactory)
);
router.post(
'/run-feature',
validatePathParams('projectPath'),
createRunFeatureHandler(autoModeService)
createRunFeatureHandler(autoModeService, facadeFactory)
);
router.post(
'/verify-feature',
validatePathParams('projectPath'),
createVerifyFeatureHandler(autoModeService)
createVerifyFeatureHandler(autoModeService, facadeFactory)
);
router.post(
'/resume-feature',
validatePathParams('projectPath'),
createResumeFeatureHandler(autoModeService)
createResumeFeatureHandler(autoModeService, facadeFactory)
);
router.post(
'/context-exists',
validatePathParams('projectPath'),
createContextExistsHandler(autoModeService)
createContextExistsHandler(autoModeService, facadeFactory)
);
router.post(
'/analyze-project',
validatePathParams('projectPath'),
createAnalyzeProjectHandler(autoModeService)
createAnalyzeProjectHandler(autoModeService, facadeFactory)
);
router.post(
'/follow-up-feature',
validatePathParams('projectPath', 'imagePaths[]'),
createFollowUpFeatureHandler(autoModeService)
createFollowUpFeatureHandler(autoModeService, facadeFactory)
);
router.post(
'/commit-feature',
validatePathParams('projectPath', 'worktreePath?'),
createCommitFeatureHandler(autoModeService)
createCommitFeatureHandler(autoModeService, facadeFactory)
);
router.post(
'/approve-plan',
validatePathParams('projectPath'),
createApprovePlanHandler(autoModeService)
createApprovePlanHandler(autoModeService, facadeFactory)
);
router.post(
'/resume-interrupted',
validatePathParams('projectPath'),
createResumeInterruptedHandler(autoModeService)
createResumeInterruptedHandler(autoModeService, facadeFactory)
);
return router;

View File

@@ -6,6 +6,7 @@ import { Router } from 'express';
import { FeatureLoader } from '../../services/feature-loader.js';
import type { SettingsService } from '../../services/settings-service.js';
import type { AutoModeService } from '../../services/auto-mode-service.js';
import type { AutoModeServiceFacade } from '../../services/auto-mode/index.js';
import type { EventEmitter } from '../../lib/events.js';
import { validatePathParams } from '../../middleware/validate-paths.js';
import { createListHandler } from './routes/list.js';
@@ -24,14 +25,15 @@ export function createFeaturesRoutes(
featureLoader: FeatureLoader,
settingsService?: SettingsService,
events?: EventEmitter,
autoModeService?: AutoModeService
autoModeService?: AutoModeService,
facadeFactory?: (projectPath: string) => AutoModeServiceFacade
): Router {
const router = Router();
router.post(
'/list',
validatePathParams('projectPath'),
createListHandler(featureLoader, autoModeService)
createListHandler(featureLoader, autoModeService, facadeFactory)
);
router.post('/get', validatePathParams('projectPath'), createGetHandler(featureLoader));
router.post(

View File

@@ -8,12 +8,17 @@
import type { Request, Response } from 'express';
import { FeatureLoader } from '../../../services/feature-loader.js';
import type { AutoModeService } from '../../../services/auto-mode-service.js';
import type { AutoModeServiceFacade } from '../../../services/auto-mode/index.js';
import { getErrorMessage, logError } from '../common.js';
import { createLogger } from '@automaker/utils';
const logger = createLogger('FeaturesListRoute');
export function createListHandler(featureLoader: FeatureLoader, autoModeService?: AutoModeService) {
export function createListHandler(
featureLoader: FeatureLoader,
autoModeService?: AutoModeService,
facadeFactory?: (projectPath: string) => AutoModeServiceFacade
) {
return async (req: Request, res: Response): Promise<void> => {
try {
const { projectPath } = req.body as { projectPath: string };
@@ -29,7 +34,21 @@ export function createListHandler(featureLoader: FeatureLoader, autoModeService?
// This detects features whose branches no longer exist (e.g., after merge/delete)
// We don't await this to keep the list response fast
// Note: detectOrphanedFeatures handles errors internally and always resolves
if (autoModeService) {
if (facadeFactory) {
const facade = facadeFactory(projectPath);
facade.detectOrphanedFeatures().then((orphanedFeatures) => {
if (orphanedFeatures.length > 0) {
logger.info(
`[ProjectLoad] Detected ${orphanedFeatures.length} orphaned feature(s) in ${projectPath}`
);
for (const { feature, missingBranch } of orphanedFeatures) {
logger.info(
`[ProjectLoad] Orphaned: ${feature.title || feature.id} - branch "${missingBranch}" no longer exists`
);
}
}
});
} else if (autoModeService) {
autoModeService.detectOrphanedFeatures(projectPath).then((orphanedFeatures) => {
if (orphanedFeatures.length > 0) {
logger.info(

View File

@@ -5,6 +5,7 @@
import { Router } from 'express';
import type { FeatureLoader } from '../../services/feature-loader.js';
import type { AutoModeService } from '../../services/auto-mode-service.js';
import type { AutoModeServiceFacade } from '../../services/auto-mode/index.js';
import type { SettingsService } from '../../services/settings-service.js';
import type { NotificationService } from '../../services/notification-service.js';
import { createOverviewHandler } from './routes/overview.js';
@@ -13,14 +14,21 @@ export function createProjectsRoutes(
featureLoader: FeatureLoader,
autoModeService: AutoModeService,
settingsService: SettingsService,
notificationService: NotificationService
notificationService: NotificationService,
facadeFactory?: (projectPath: string) => AutoModeServiceFacade
): Router {
const router = Router();
// GET /overview - Get aggregate status for all projects
router.get(
'/overview',
createOverviewHandler(featureLoader, autoModeService, settingsService, notificationService)
createOverviewHandler(
featureLoader,
autoModeService,
settingsService,
notificationService,
facadeFactory
)
);
return router;

View File

@@ -10,6 +10,11 @@
import type { Request, Response } from 'express';
import type { FeatureLoader } from '../../../services/feature-loader.js';
import type { AutoModeService } from '../../../services/auto-mode-service.js';
import type {
AutoModeServiceFacade,
RunningAgentInfo,
ProjectAutoModeStatus,
} from '../../../services/auto-mode/index.js';
import type { SettingsService } from '../../../services/settings-service.js';
import type { NotificationService } from '../../../services/notification-service.js';
import type {
@@ -149,7 +154,8 @@ export function createOverviewHandler(
featureLoader: FeatureLoader,
autoModeService: AutoModeService,
settingsService: SettingsService,
notificationService: NotificationService
notificationService: NotificationService,
facadeFactory?: (projectPath: string) => AutoModeServiceFacade
) {
return async (_req: Request, res: Response): Promise<void> => {
try {
@@ -158,7 +164,15 @@ export function createOverviewHandler(
const projectRefs: ProjectRef[] = settings.projects || [];
// Get all running agents once to count live running features per project
const allRunningAgents = await autoModeService.getRunningAgents();
// Use facade if available, otherwise fall back to autoModeService
let allRunningAgents: RunningAgentInfo[];
if (facadeFactory && projectRefs.length > 0) {
// For running agents, we can use any project's facade since it's a global query
const facade = facadeFactory(projectRefs[0].path);
allRunningAgents = await facade.getRunningAgents();
} else {
allRunningAgents = await autoModeService.getRunningAgents();
}
// Collect project statuses in parallel
const projectStatusPromises = projectRefs.map(async (projectRef): Promise<ProjectStatus> => {
@@ -169,7 +183,13 @@ export function createOverviewHandler(
const totalFeatures = features.length;
// Get auto-mode status for this project (main worktree, branchName = null)
const autoModeStatus = autoModeService.getStatusForProject(projectRef.path, null);
let autoModeStatus: ProjectAutoModeStatus;
if (facadeFactory) {
const facade = facadeFactory(projectRef.path);
autoModeStatus = facade.getStatusForProject(null);
} else {
autoModeStatus = autoModeService.getStatusForProject(projectRef.path, null);
}
const isAutoModeRunning = autoModeStatus.isAutoLoopRunning;
// Count live running features for this project (across all branches)