mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-03 08:53:36 +00:00
adding a queue system to the agent runner
This commit is contained in:
147
apps/server/src/routes/backlog-plan/routes/apply.ts
Normal file
147
apps/server/src/routes/backlog-plan/routes/apply.ts
Normal file
@@ -0,0 +1,147 @@
|
||||
/**
|
||||
* POST /apply endpoint - Apply a backlog plan
|
||||
*/
|
||||
|
||||
import type { Request, Response } from 'express';
|
||||
import type { BacklogPlanResult, BacklogChange, Feature } from '@automaker/types';
|
||||
import { FeatureLoader } from '../../../services/feature-loader.js';
|
||||
import { getErrorMessage, logError, logger } from '../common.js';
|
||||
|
||||
const featureLoader = new FeatureLoader();
|
||||
|
||||
export function createApplyHandler() {
|
||||
return async (req: Request, res: Response): Promise<void> => {
|
||||
try {
|
||||
const { projectPath, plan } = req.body as {
|
||||
projectPath: string;
|
||||
plan: BacklogPlanResult;
|
||||
};
|
||||
|
||||
if (!projectPath) {
|
||||
res.status(400).json({ success: false, error: 'projectPath required' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!plan || !plan.changes) {
|
||||
res.status(400).json({ success: false, error: 'plan with changes required' });
|
||||
return;
|
||||
}
|
||||
|
||||
const appliedChanges: string[] = [];
|
||||
|
||||
// Load current features for dependency validation
|
||||
const allFeatures = await featureLoader.getAll(projectPath);
|
||||
const featureMap = new Map(allFeatures.map((f) => [f.id, f]));
|
||||
|
||||
// Process changes in order: deletes first, then adds, then updates
|
||||
// This ensures we can remove dependencies before they cause issues
|
||||
|
||||
// 1. First pass: Handle deletes
|
||||
const deletions = plan.changes.filter((c) => c.type === 'delete');
|
||||
for (const change of deletions) {
|
||||
if (!change.featureId) continue;
|
||||
|
||||
try {
|
||||
// Before deleting, update any features that depend on this one
|
||||
for (const feature of allFeatures) {
|
||||
if (feature.dependencies?.includes(change.featureId)) {
|
||||
const newDeps = feature.dependencies.filter((d) => d !== change.featureId);
|
||||
await featureLoader.update(projectPath, feature.id, { dependencies: newDeps });
|
||||
logger.info(
|
||||
`[BacklogPlan] Removed dependency ${change.featureId} from ${feature.id}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Now delete the feature
|
||||
const deleted = await featureLoader.delete(projectPath, change.featureId);
|
||||
if (deleted) {
|
||||
appliedChanges.push(`deleted:${change.featureId}`);
|
||||
featureMap.delete(change.featureId);
|
||||
logger.info(`[BacklogPlan] Deleted feature ${change.featureId}`);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
`[BacklogPlan] Failed to delete ${change.featureId}:`,
|
||||
getErrorMessage(error)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Second pass: Handle adds
|
||||
const additions = plan.changes.filter((c) => c.type === 'add');
|
||||
for (const change of additions) {
|
||||
if (!change.feature) continue;
|
||||
|
||||
try {
|
||||
// Create the new feature
|
||||
const newFeature = await featureLoader.create(projectPath, {
|
||||
title: change.feature.title,
|
||||
description: change.feature.description || '',
|
||||
category: change.feature.category || 'Uncategorized',
|
||||
dependencies: change.feature.dependencies,
|
||||
priority: change.feature.priority,
|
||||
status: 'backlog',
|
||||
});
|
||||
|
||||
appliedChanges.push(`added:${newFeature.id}`);
|
||||
featureMap.set(newFeature.id, newFeature);
|
||||
logger.info(`[BacklogPlan] Created feature ${newFeature.id}: ${newFeature.title}`);
|
||||
} catch (error) {
|
||||
logger.error(`[BacklogPlan] Failed to add feature:`, getErrorMessage(error));
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Third pass: Handle updates
|
||||
const updates = plan.changes.filter((c) => c.type === 'update');
|
||||
for (const change of updates) {
|
||||
if (!change.featureId || !change.feature) continue;
|
||||
|
||||
try {
|
||||
const updated = await featureLoader.update(projectPath, change.featureId, change.feature);
|
||||
appliedChanges.push(`updated:${change.featureId}`);
|
||||
featureMap.set(change.featureId, updated);
|
||||
logger.info(`[BacklogPlan] Updated feature ${change.featureId}`);
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
`[BacklogPlan] Failed to update ${change.featureId}:`,
|
||||
getErrorMessage(error)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Apply dependency updates from the plan
|
||||
if (plan.dependencyUpdates) {
|
||||
for (const depUpdate of plan.dependencyUpdates) {
|
||||
try {
|
||||
const feature = featureMap.get(depUpdate.featureId);
|
||||
if (feature) {
|
||||
const currentDeps = feature.dependencies || [];
|
||||
const newDeps = currentDeps
|
||||
.filter((d) => !depUpdate.removedDependencies.includes(d))
|
||||
.concat(depUpdate.addedDependencies.filter((d) => !currentDeps.includes(d)));
|
||||
|
||||
await featureLoader.update(projectPath, depUpdate.featureId, {
|
||||
dependencies: newDeps,
|
||||
});
|
||||
logger.info(`[BacklogPlan] Updated dependencies for ${depUpdate.featureId}`);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
`[BacklogPlan] Failed to update dependencies for ${depUpdate.featureId}:`,
|
||||
getErrorMessage(error)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
appliedChanges,
|
||||
});
|
||||
} catch (error) {
|
||||
logError(error, 'Apply backlog plan failed');
|
||||
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
||||
}
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user