mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-03 21:03:08 +00:00
feat: Add task dependencies and spawn sub-task functionality
- Add edge dragging to create dependencies in graph view - Add spawn sub-task action available in graph view and kanban board - Implement ancestor context selection when spawning tasks - Add dependency validation (circular, self, duplicate prevention) - Include ancestor context in spawned task descriptions 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -19,14 +19,7 @@ import {
|
||||
FeatureTextFilePath as DescriptionTextFilePath,
|
||||
ImagePreviewMap,
|
||||
} from '@/components/ui/description-image-dropzone';
|
||||
import {
|
||||
MessageSquare,
|
||||
Settings2,
|
||||
SlidersHorizontal,
|
||||
FlaskConical,
|
||||
Sparkles,
|
||||
ChevronDown,
|
||||
} from 'lucide-react';
|
||||
import { MessageSquare, Settings2, SlidersHorizontal, Sparkles, ChevronDown } from 'lucide-react';
|
||||
import { toast } from 'sonner';
|
||||
import { getElectronAPI } from '@/lib/electron';
|
||||
import { modelSupportsThinking } from '@/lib/utils';
|
||||
@@ -37,6 +30,7 @@ import {
|
||||
FeatureImage,
|
||||
AIProfile,
|
||||
PlanningMode,
|
||||
Feature,
|
||||
} from '@/store/app-store';
|
||||
import {
|
||||
ModelSelector,
|
||||
@@ -46,6 +40,7 @@ import {
|
||||
PrioritySelector,
|
||||
BranchSelector,
|
||||
PlanningModeSelector,
|
||||
AncestorContextSection,
|
||||
} from '../shared';
|
||||
import {
|
||||
DropdownMenu,
|
||||
@@ -54,6 +49,11 @@ import {
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import { useNavigate } from '@tanstack/react-router';
|
||||
import {
|
||||
getAncestors,
|
||||
formatAncestorContextForPrompt,
|
||||
AncestorContext,
|
||||
} from '@/components/views/graph-view/utils';
|
||||
|
||||
interface AddFeatureDialogProps {
|
||||
open: boolean;
|
||||
@@ -72,6 +72,7 @@ interface AddFeatureDialogProps {
|
||||
priority: number;
|
||||
planningMode: PlanningMode;
|
||||
requirePlanApproval: boolean;
|
||||
dependencies?: string[];
|
||||
}) => void;
|
||||
categorySuggestions: string[];
|
||||
branchSuggestions: string[];
|
||||
@@ -82,6 +83,9 @@ interface AddFeatureDialogProps {
|
||||
isMaximized: boolean;
|
||||
showProfilesOnly: boolean;
|
||||
aiProfiles: AIProfile[];
|
||||
// Spawn task mode props
|
||||
parentFeature?: Feature | null;
|
||||
allFeatures?: Feature[];
|
||||
}
|
||||
|
||||
export function AddFeatureDialog({
|
||||
@@ -97,7 +101,10 @@ export function AddFeatureDialog({
|
||||
isMaximized,
|
||||
showProfilesOnly,
|
||||
aiProfiles,
|
||||
parentFeature = null,
|
||||
allFeatures = [],
|
||||
}: AddFeatureDialogProps) {
|
||||
const isSpawnMode = !!parentFeature;
|
||||
const navigate = useNavigate();
|
||||
const [useCurrentBranch, setUseCurrentBranch] = useState(true);
|
||||
const [newFeature, setNewFeature] = useState({
|
||||
@@ -125,6 +132,10 @@ export function AddFeatureDialog({
|
||||
const [planningMode, setPlanningMode] = useState<PlanningMode>('skip');
|
||||
const [requirePlanApproval, setRequirePlanApproval] = useState(false);
|
||||
|
||||
// Spawn mode state
|
||||
const [ancestors, setAncestors] = useState<AncestorContext[]>([]);
|
||||
const [selectedAncestorIds, setSelectedAncestorIds] = useState<Set<string>>(new Set());
|
||||
|
||||
// Get enhancement model, planning mode defaults, and worktrees setting from store
|
||||
const {
|
||||
enhancementModel,
|
||||
@@ -153,6 +164,18 @@ export function AddFeatureDialog({
|
||||
setUseCurrentBranch(true);
|
||||
setPlanningMode(defaultPlanningMode);
|
||||
setRequirePlanApproval(defaultRequirePlanApproval);
|
||||
|
||||
// Initialize ancestors for spawn mode
|
||||
if (parentFeature) {
|
||||
const ancestorList = getAncestors(parentFeature, allFeatures);
|
||||
setAncestors(ancestorList);
|
||||
// Select all ancestors by default (including parent)
|
||||
const allIds = new Set([parentFeature.id, ...ancestorList.map((a) => a.id)]);
|
||||
setSelectedAncestorIds(allIds);
|
||||
} else {
|
||||
setAncestors([]);
|
||||
setSelectedAncestorIds(new Set());
|
||||
}
|
||||
}
|
||||
}, [
|
||||
open,
|
||||
@@ -162,6 +185,8 @@ export function AddFeatureDialog({
|
||||
defaultRequirePlanApproval,
|
||||
defaultAIProfileId,
|
||||
aiProfiles,
|
||||
parentFeature,
|
||||
allFeatures,
|
||||
]);
|
||||
|
||||
const handleAdd = () => {
|
||||
@@ -187,10 +212,34 @@ export function AddFeatureDialog({
|
||||
// Otherwise (primary worktree), use empty string which means "unassigned" (show only on primary)
|
||||
const finalBranchName = useCurrentBranch ? currentBranch || '' : newFeature.branchName || '';
|
||||
|
||||
// Build final description - prepend ancestor context in spawn mode
|
||||
let finalDescription = newFeature.description;
|
||||
if (isSpawnMode && parentFeature && selectedAncestorIds.size > 0) {
|
||||
// Create parent context as an AncestorContext
|
||||
const parentContext: AncestorContext = {
|
||||
id: parentFeature.id,
|
||||
title: parentFeature.title,
|
||||
description: parentFeature.description,
|
||||
spec: parentFeature.spec,
|
||||
summary: parentFeature.summary,
|
||||
depth: -1,
|
||||
};
|
||||
|
||||
const allAncestorsWithParent = [parentContext, ...ancestors];
|
||||
const contextText = formatAncestorContextForPrompt(
|
||||
allAncestorsWithParent,
|
||||
selectedAncestorIds
|
||||
);
|
||||
|
||||
if (contextText) {
|
||||
finalDescription = `${contextText}\n\n---\n\n## Task Description\n\n${newFeature.description}`;
|
||||
}
|
||||
}
|
||||
|
||||
onAdd({
|
||||
title: newFeature.title,
|
||||
category,
|
||||
description: newFeature.description,
|
||||
description: finalDescription,
|
||||
images: newFeature.images,
|
||||
imagePaths: newFeature.imagePaths,
|
||||
textFilePaths: newFeature.textFilePaths,
|
||||
@@ -201,6 +250,8 @@ export function AddFeatureDialog({
|
||||
priority: newFeature.priority,
|
||||
planningMode,
|
||||
requirePlanApproval,
|
||||
// In spawn mode, automatically add parent as dependency
|
||||
dependencies: isSpawnMode && parentFeature ? [parentFeature.id] : undefined,
|
||||
});
|
||||
|
||||
// Reset form
|
||||
@@ -299,8 +350,12 @@ export function AddFeatureDialog({
|
||||
}}
|
||||
>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Add New Feature</DialogTitle>
|
||||
<DialogDescription>Create a new feature card for the Kanban board.</DialogDescription>
|
||||
<DialogTitle>{isSpawnMode ? 'Spawn Sub-Task' : 'Add New Feature'}</DialogTitle>
|
||||
<DialogDescription>
|
||||
{isSpawnMode
|
||||
? `Create a sub-task that depends on "${parentFeature?.title || parentFeature?.description.slice(0, 50)}..."`
|
||||
: 'Create a new feature card for the Kanban board.'}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<Tabs defaultValue="prompt" className="py-4 flex-1 min-h-0 flex flex-col">
|
||||
<TabsList className="w-full grid grid-cols-3 mb-4">
|
||||
@@ -320,6 +375,22 @@ export function AddFeatureDialog({
|
||||
|
||||
{/* Prompt Tab */}
|
||||
<TabsContent value="prompt" className="space-y-4 overflow-y-auto cursor-default">
|
||||
{/* Ancestor Context Section - only in spawn mode */}
|
||||
{isSpawnMode && parentFeature && (
|
||||
<AncestorContextSection
|
||||
parentFeature={{
|
||||
id: parentFeature.id,
|
||||
title: parentFeature.title,
|
||||
description: parentFeature.description,
|
||||
spec: parentFeature.spec,
|
||||
summary: parentFeature.summary,
|
||||
}}
|
||||
ancestors={ancestors}
|
||||
selectedAncestorIds={selectedAncestorIds}
|
||||
onSelectionChange={setSelectedAncestorIds}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="description">Description</Label>
|
||||
<DescriptionImageDropZone
|
||||
@@ -512,7 +583,7 @@ export function AddFeatureDialog({
|
||||
data-testid="confirm-add-feature"
|
||||
disabled={useWorktrees && !useCurrentBranch && !newFeature.branchName.trim()}
|
||||
>
|
||||
Add Feature
|
||||
{isSpawnMode ? 'Spawn Task' : 'Add Feature'}
|
||||
</HotkeyButton>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
|
||||
Reference in New Issue
Block a user