mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-03-16 21:53:07 +00:00
refactor(auto-mode): convert getStatusForProject to async and enhance feature retrieval
- Updated getStatusForProject method in AutoModeServiceCompat and its facade to be asynchronous, allowing for better handling of feature status retrieval. - Modified related status handlers in the server routes to await the updated method. - Introduced a new method, getRunningFeaturesForWorktree, in ConcurrencyManager to improve feature ID retrieval based on branch normalization. - Adjusted BoardView component to ensure consistent handling of running auto tasks across worktrees. These changes improve the responsiveness and accuracy of the auto mode feature in the application.
This commit is contained in:
@@ -25,7 +25,7 @@ export function createStatusHandler(autoModeService: AutoModeServiceCompat) {
|
||||
// Normalize branchName: undefined becomes null
|
||||
const normalizedBranchName = branchName ?? null;
|
||||
|
||||
const projectStatus = autoModeService.getStatusForProject(
|
||||
const projectStatus = await autoModeService.getStatusForProject(
|
||||
projectPath,
|
||||
normalizedBranchName
|
||||
);
|
||||
|
||||
@@ -173,7 +173,7 @@ export function createOverviewHandler(
|
||||
const totalFeatures = features.length;
|
||||
|
||||
// Get auto-mode status for this project (main worktree, branchName = null)
|
||||
const autoModeStatus: ProjectAutoModeStatus = autoModeService.getStatusForProject(
|
||||
const autoModeStatus: ProjectAutoModeStatus = await autoModeService.getStatusForProject(
|
||||
projectRef.path,
|
||||
null
|
||||
);
|
||||
|
||||
@@ -89,16 +89,16 @@ export class AutoModeServiceCompat {
|
||||
// PER-PROJECT OPERATIONS (delegated to facades)
|
||||
// ===========================================================================
|
||||
|
||||
getStatusForProject(
|
||||
async getStatusForProject(
|
||||
projectPath: string,
|
||||
branchName: string | null = null
|
||||
): {
|
||||
): Promise<{
|
||||
isAutoLoopRunning: boolean;
|
||||
runningFeatures: string[];
|
||||
runningCount: number;
|
||||
maxConcurrency: number;
|
||||
branchName: string | null;
|
||||
} {
|
||||
}> {
|
||||
const facade = this.createFacade(projectPath);
|
||||
return facade.getStatusForProject(branchName);
|
||||
}
|
||||
|
||||
@@ -757,7 +757,7 @@ Address the follow-up instructions above. Review the previous work and make the
|
||||
* Get status for this project/worktree
|
||||
* @param branchName - The branch name, or null for main worktree
|
||||
*/
|
||||
getStatusForProject(branchName: string | null = null): ProjectAutoModeStatus {
|
||||
async getStatusForProject(branchName: string | null = null): Promise<ProjectAutoModeStatus> {
|
||||
const isAutoLoopRunning = this.autoLoopCoordinator.isAutoLoopRunningForProject(
|
||||
this.projectPath,
|
||||
branchName
|
||||
@@ -766,10 +766,12 @@ Address the follow-up instructions above. Review the previous work and make the
|
||||
this.projectPath,
|
||||
branchName
|
||||
);
|
||||
const runningFeatures = this.concurrencyManager
|
||||
.getAllRunning()
|
||||
.filter((f) => f.projectPath === this.projectPath && f.branchName === branchName)
|
||||
.map((f) => f.featureId);
|
||||
// Use branchName-normalized filter so features with branchName "main"
|
||||
// are correctly matched when querying for the main worktree (null)
|
||||
const runningFeatures = await this.concurrencyManager.getRunningFeaturesForWorktree(
|
||||
this.projectPath,
|
||||
branchName
|
||||
);
|
||||
|
||||
return {
|
||||
isAutoLoopRunning,
|
||||
|
||||
@@ -209,6 +209,41 @@ export class ConcurrencyManager {
|
||||
return Array.from(this.runningFeatures.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get running feature IDs for a specific worktree, with proper primary branch normalization.
|
||||
*
|
||||
* When branchName is null (main worktree), matches features with branchName === null
|
||||
* OR branchName matching the primary branch (e.g., "main", "master").
|
||||
*
|
||||
* @param projectPath - The project path
|
||||
* @param branchName - The branch name, or null for main worktree
|
||||
* @returns Array of feature IDs running in the specified worktree
|
||||
*/
|
||||
async getRunningFeaturesForWorktree(
|
||||
projectPath: string,
|
||||
branchName: string | null
|
||||
): Promise<string[]> {
|
||||
const primaryBranch = await this.getCurrentBranch(projectPath);
|
||||
const featureIds: string[] = [];
|
||||
|
||||
for (const [, feature] of this.runningFeatures) {
|
||||
if (feature.projectPath !== projectPath) continue;
|
||||
const featureBranch = feature.branchName ?? null;
|
||||
|
||||
if (branchName === null) {
|
||||
// Main worktree: match features with null branchName OR primary branch name
|
||||
const isPrimaryBranch =
|
||||
featureBranch === null || (primaryBranch && featureBranch === primaryBranch);
|
||||
if (isPrimaryBranch) featureIds.push(feature.featureId);
|
||||
} else {
|
||||
// Feature worktree: exact match
|
||||
if (featureBranch === branchName) featureIds.push(feature.featureId);
|
||||
}
|
||||
}
|
||||
|
||||
return featureIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update properties of a running feature
|
||||
*
|
||||
|
||||
@@ -593,7 +593,7 @@ export function BoardView() {
|
||||
} = useBoardActions({
|
||||
currentProject,
|
||||
features: hookFeatures,
|
||||
runningAutoTasks,
|
||||
runningAutoTasks: runningAutoTasksAllWorktrees,
|
||||
loadFeatures,
|
||||
persistFeatureCreate,
|
||||
persistFeatureUpdate,
|
||||
@@ -1092,7 +1092,7 @@ export function BoardView() {
|
||||
} = useBoardDragDrop({
|
||||
features: hookFeatures,
|
||||
currentProject,
|
||||
runningAutoTasks,
|
||||
runningAutoTasks: runningAutoTasksAllWorktrees,
|
||||
persistFeatureUpdate,
|
||||
handleStartImplementation,
|
||||
});
|
||||
@@ -1472,7 +1472,7 @@ export function BoardView() {
|
||||
setShowAddDialog(true);
|
||||
},
|
||||
}}
|
||||
runningAutoTasks={runningAutoTasks}
|
||||
runningAutoTasks={runningAutoTasksAllWorktrees}
|
||||
pipelineConfig={pipelineConfig}
|
||||
onAddFeature={() => setShowAddDialog(true)}
|
||||
isSelectionMode={isSelectionMode}
|
||||
@@ -1511,7 +1511,7 @@ export function BoardView() {
|
||||
setShowAddDialog(true);
|
||||
}}
|
||||
featuresWithContext={featuresWithContext}
|
||||
runningAutoTasks={runningAutoTasks}
|
||||
runningAutoTasks={runningAutoTasksAllWorktrees}
|
||||
onArchiveAllVerified={() => setShowArchiveAllVerifiedDialog(true)}
|
||||
onAddFeature={() => setShowAddDialog(true)}
|
||||
onShowCompletedModal={() => setShowCompletedModal(true)}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { createLogger } from '@automaker/utils/logger';
|
||||
import { DragStartEvent, DragEndEvent } from '@dnd-kit/core';
|
||||
import { Feature } from '@/store/app-store';
|
||||
import { useAppStore } from '@/store/app-store';
|
||||
import { useAutoMode } from '@/hooks/use-auto-mode';
|
||||
import { toast } from 'sonner';
|
||||
import { COLUMNS, ColumnId } from '../constants';
|
||||
|
||||
@@ -33,6 +34,7 @@ export function useBoardDragDrop({
|
||||
null
|
||||
);
|
||||
const { moveFeature, updateFeature } = useAppStore();
|
||||
const autoMode = useAutoMode();
|
||||
|
||||
// Note: getOrCreateWorktreeForFeature removed - worktrees are now created server-side
|
||||
// at execution time based on feature.branchName
|
||||
@@ -155,19 +157,9 @@ export function useBoardDragDrop({
|
||||
}
|
||||
}
|
||||
|
||||
// Determine if dragging is allowed based on status and skipTests
|
||||
// - Backlog items can always be dragged
|
||||
// - waiting_approval items can always be dragged (to allow manual verification via drag)
|
||||
// - verified items can always be dragged (to allow moving back to waiting_approval)
|
||||
// - in_progress items can be dragged (but not if they're currently running)
|
||||
// - Non-skipTests (TDD) items that are in progress cannot be dragged if they are running
|
||||
if (draggedFeature.status === 'in_progress') {
|
||||
// Only allow dragging in_progress if it's not currently running
|
||||
if (isRunningTask) {
|
||||
logger.debug('Cannot drag feature - currently running');
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Determine if dragging is allowed based on status
|
||||
// Running in_progress features CAN be dragged to backlog (stops the agent)
|
||||
// but cannot be dragged to other columns
|
||||
|
||||
let targetStatus: ColumnId | null = null;
|
||||
|
||||
@@ -235,15 +227,38 @@ export function useBoardDragDrop({
|
||||
} else if (draggedFeature.status === 'in_progress') {
|
||||
// Handle in_progress features being moved
|
||||
if (targetStatus === 'backlog') {
|
||||
// Allow moving in_progress cards back to backlog
|
||||
// If the feature is currently running, stop it first
|
||||
if (isRunningTask) {
|
||||
try {
|
||||
await autoMode.stopFeature(featureId);
|
||||
logger.info('Stopped running feature via drag to backlog:', featureId);
|
||||
} catch (error) {
|
||||
logger.error('Error stopping feature during drag to backlog:', error);
|
||||
toast.error('Failed to stop agent', {
|
||||
description: 'The feature will still be moved to backlog.',
|
||||
});
|
||||
}
|
||||
}
|
||||
moveFeature(featureId, 'backlog');
|
||||
persistFeatureUpdate(featureId, { status: 'backlog' });
|
||||
toast.info('Feature moved to backlog', {
|
||||
description: `Moved to Backlog: ${draggedFeature.description.slice(
|
||||
0,
|
||||
50
|
||||
)}${draggedFeature.description.length > 50 ? '...' : ''}`,
|
||||
toast.info(
|
||||
isRunningTask
|
||||
? 'Agent stopped and feature moved to backlog'
|
||||
: 'Feature moved to backlog',
|
||||
{
|
||||
description: `Moved to Backlog: ${draggedFeature.description.slice(
|
||||
0,
|
||||
50
|
||||
)}${draggedFeature.description.length > 50 ? '...' : ''}`,
|
||||
}
|
||||
);
|
||||
} else if (isRunningTask) {
|
||||
// Running features can only be dragged to backlog, not other columns
|
||||
logger.debug('Cannot drag running feature to', targetStatus);
|
||||
toast.error('Cannot move running feature', {
|
||||
description: 'Stop the agent first or drag to Backlog to stop and move.',
|
||||
});
|
||||
return;
|
||||
} else if (targetStatus === 'verified' && draggedFeature.skipTests) {
|
||||
// Manual verify via drag (only for skipTests features)
|
||||
moveFeature(featureId, 'verified');
|
||||
@@ -310,6 +325,7 @@ export function useBoardDragDrop({
|
||||
updateFeature,
|
||||
persistFeatureUpdate,
|
||||
handleStartImplementation,
|
||||
autoMode,
|
||||
]
|
||||
);
|
||||
|
||||
|
||||
@@ -28,8 +28,11 @@ const PROGRESS_DEBOUNCE_MAX_WAIT = 2000;
|
||||
* feature moving to custom pipeline columns (fixes GitHub issue #668)
|
||||
*/
|
||||
const FEATURE_LIST_INVALIDATION_EVENTS: AutoModeEvent['type'][] = [
|
||||
'auto_mode_feature_start',
|
||||
'auto_mode_feature_complete',
|
||||
'auto_mode_error',
|
||||
'auto_mode_started',
|
||||
'auto_mode_stopped',
|
||||
'plan_approval_required',
|
||||
'plan_approved',
|
||||
'plan_rejected',
|
||||
@@ -39,11 +42,11 @@ const FEATURE_LIST_INVALIDATION_EVENTS: AutoModeEvent['type'][] = [
|
||||
|
||||
/**
|
||||
* Events that should invalidate a specific feature (features.single query)
|
||||
* Note: pipeline_step_started is NOT included here because it already invalidates
|
||||
* features.all() above, which also invalidates child queries (features.single)
|
||||
* Note: auto_mode_feature_start and pipeline_step_started are NOT included here
|
||||
* because they already invalidate features.all() above, which also invalidates
|
||||
* child queries (features.single)
|
||||
*/
|
||||
const SINGLE_FEATURE_INVALIDATION_EVENTS: AutoModeEvent['type'][] = [
|
||||
'auto_mode_feature_start',
|
||||
'auto_mode_phase',
|
||||
'auto_mode_phase_complete',
|
||||
'auto_mode_task_status',
|
||||
|
||||
Reference in New Issue
Block a user