feat: implement XML extraction utilities and enhance feature handling

- Introduced a new xml-extractor module with functions for XML parsing, including escaping/unescaping XML characters, extracting sections and elements, and managing implemented features.
- Added functionality to add, remove, update, and check for implemented features in the app_spec.txt file.
- Enhanced the create and update feature handlers to check for duplicate titles and trigger synchronization with app_spec.txt on status changes.
- Updated tests to cover new XML extraction utilities and feature handling logic, ensuring robust functionality and reliability.
This commit is contained in:
Kacper
2026-01-16 22:55:10 +01:00
parent fd1727a443
commit b263cc615e
8 changed files with 2153 additions and 2 deletions

View File

@@ -23,6 +23,19 @@ export function createCreateHandler(featureLoader: FeatureLoader) {
return;
}
// Check for duplicate title if title is provided
if (feature.title && feature.title.trim()) {
const duplicate = await featureLoader.findDuplicateTitle(projectPath, feature.title);
if (duplicate) {
res.status(409).json({
success: false,
error: `A feature with title "${feature.title}" already exists`,
duplicateFeatureId: duplicate.id,
});
return;
}
}
const created = await featureLoader.create(projectPath, feature);
res.json({ success: true, feature: created });
} catch (error) {

View File

@@ -4,8 +4,14 @@
import type { Request, Response } from 'express';
import { FeatureLoader } from '../../../services/feature-loader.js';
import type { Feature } from '@automaker/types';
import type { Feature, FeatureStatus } from '@automaker/types';
import { getErrorMessage, logError } from '../common.js';
import { createLogger } from '@automaker/utils';
const logger = createLogger('features/update');
// Statuses that should trigger syncing to app_spec.txt
const SYNC_TRIGGER_STATUSES: FeatureStatus[] = ['verified', 'completed'];
export function createUpdateHandler(featureLoader: FeatureLoader) {
return async (req: Request, res: Response): Promise<void> => {
@@ -34,6 +40,28 @@ export function createUpdateHandler(featureLoader: FeatureLoader) {
return;
}
// Check for duplicate title if title is being updated
if (updates.title && updates.title.trim()) {
const duplicate = await featureLoader.findDuplicateTitle(
projectPath,
updates.title,
featureId // Exclude the current feature from duplicate check
);
if (duplicate) {
res.status(409).json({
success: false,
error: `A feature with title "${updates.title}" already exists`,
duplicateFeatureId: duplicate.id,
});
return;
}
}
// Get the current feature to detect status changes
const currentFeature = await featureLoader.get(projectPath, featureId);
const previousStatus = currentFeature?.status as FeatureStatus | undefined;
const newStatus = updates.status as FeatureStatus | undefined;
const updated = await featureLoader.update(
projectPath,
featureId,
@@ -42,6 +70,22 @@ export function createUpdateHandler(featureLoader: FeatureLoader) {
enhancementMode,
preEnhancementDescription
);
// Trigger sync to app_spec.txt when status changes to verified or completed
if (newStatus && SYNC_TRIGGER_STATUSES.includes(newStatus) && previousStatus !== newStatus) {
try {
const synced = await featureLoader.syncFeatureToAppSpec(projectPath, updated);
if (synced) {
logger.info(
`Synced feature "${updated.title || updated.id}" to app_spec.txt on status change to ${newStatus}`
);
}
} catch (syncError) {
// Log the sync error but don't fail the update operation
logger.error(`Failed to sync feature to app_spec.txt:`, syncError);
}
}
res.json({ success: true, feature: updated });
} catch (error) {
logError(error, 'Update feature failed');