feat(extension): Add scope-up and scope-down to VS Code extension task details
- Added useScopeUpTask and useScopeDownTask hooks in useTaskQueries.ts - Enhanced AIActionsSection with Task Complexity Adjustment section - Added strength selection (light/regular/heavy) and custom prompt support - Integrated scope buttons with proper loading states and error handling - Uses existing mcpRequest handler for scope_up_task and scope_down_task tools - Maintains consistent UI patterns with existing AI actions Extension now supports dynamic task complexity adjustment directly from task details view.
This commit is contained in:
committed by
Ralph Khreish
parent
2b79822002
commit
451a55d44f
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"meta": {
|
||||
"generatedAt": "2025-08-02T09:50:02.895Z",
|
||||
"generatedAt": "2025-08-02T14:28:59.851Z",
|
||||
"tasksAnalyzed": 1,
|
||||
"totalTasks": 93,
|
||||
"analysisCount": 1,
|
||||
@@ -10,12 +10,12 @@
|
||||
},
|
||||
"complexityAnalysis": [
|
||||
{
|
||||
"taskId": 104,
|
||||
"taskTitle": "Implement 'scope-up' and 'scope-down' CLI Commands for Dynamic Task Complexity Adjustment",
|
||||
"taskId": 24,
|
||||
"taskTitle": "Implement AI-Powered Test Generation Command",
|
||||
"complexityScore": 8,
|
||||
"recommendedSubtasks": 6,
|
||||
"expansionPrompt": "Break down the implementation of the 'scope-up' and 'scope-down' CLI commands into detailed subtasks, focusing on: 1) Command structure and argument parsing, 2) Task/subtask retrieval and context gathering, 3) AI prompt construction for different strength levels, 4) Response processing and task updating, 5) Error handling and user feedback, and 6) Integration with existing systems including MCP tool. For each subtask, include specific implementation details, dependencies, and test strategies.",
|
||||
"reasoning": "This task has high complexity (8/10) due to several factors: 1) It requires integration with multiple existing systems (CLI, AI services, task management), 2) It involves sophisticated AI prompt engineering with context awareness, 3) It needs to handle various parameters and edge cases, 4) It must maintain data integrity across task modifications, and 5) It requires both CLI and MCP tool implementations. The task already has 5 subtasks that cover the main components, but could benefit from one additional subtask focused on testing and documentation."
|
||||
"expansionPrompt": "Expand task 24 'Implement AI-Powered Test Generation Command' into 6 subtasks, focusing on: 1) Command structure implementation, 2) AI prompt engineering for test generation, 3) Test file generation and output, 4) Framework-specific template implementation, 5) MCP tool integration, and 6) Documentation and help system integration. Include detailed implementation steps, dependencies, and testing approaches for each subtask.",
|
||||
"reasoning": "This task has high complexity due to several challenging aspects: 1) AI integration requiring sophisticated prompt engineering, 2) Test generation across multiple frameworks, 3) File system operations with proper error handling, 4) MCP tool integration, 5) Complex configuration requirements, and 6) Framework-specific template generation. The task already has 5 subtasks but could benefit from reorganization based on the updated implementation details in the info blocks, particularly around framework support and configuration."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -4,10 +4,12 @@ import { Button } from '@/components/ui/button';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { CollapsibleSection } from '@/components/ui/CollapsibleSection';
|
||||
import { Wand2, Loader2, PlusCircle } from 'lucide-react';
|
||||
import { Wand2, Loader2, PlusCircle, TrendingUp, TrendingDown } from 'lucide-react';
|
||||
import {
|
||||
useUpdateTask,
|
||||
useUpdateSubtask
|
||||
useUpdateSubtask,
|
||||
useScopeUpTask,
|
||||
useScopeDownTask
|
||||
} from '../../webview/hooks/useTaskQueries';
|
||||
import type { TaskMasterTask } from '../../webview/types';
|
||||
|
||||
@@ -31,11 +33,15 @@ export const AIActionsSection: React.FC<AIActionsSectionProps> = ({
|
||||
onAppendingChange
|
||||
}) => {
|
||||
const [prompt, setPrompt] = useState('');
|
||||
const [lastAction, setLastAction] = useState<'regenerate' | 'append' | null>(
|
||||
const [scopePrompt, setScopePrompt] = useState('');
|
||||
const [scopeStrength, setScopeStrength] = useState<'light' | 'regular' | 'heavy'>('regular');
|
||||
const [lastAction, setLastAction] = useState<'regenerate' | 'append' | 'scope-up' | 'scope-down' | null>(
|
||||
null
|
||||
);
|
||||
const updateTask = useUpdateTask();
|
||||
const updateSubtask = useUpdateSubtask();
|
||||
const scopeUpTask = useScopeUpTask();
|
||||
const scopeDownTask = useScopeDownTask();
|
||||
|
||||
const handleRegenerate = async () => {
|
||||
if (!currentTask || !prompt.trim()) {
|
||||
@@ -103,10 +109,64 @@ export const AIActionsSection: React.FC<AIActionsSectionProps> = ({
|
||||
}
|
||||
};
|
||||
|
||||
const handleScopeUp = async () => {
|
||||
if (!currentTask) {
|
||||
return;
|
||||
}
|
||||
|
||||
setLastAction('scope-up');
|
||||
|
||||
try {
|
||||
const taskId = isSubtask && parentTask ? `${parentTask.id}.${currentTask.id}` : currentTask.id;
|
||||
|
||||
await scopeUpTask.mutateAsync({
|
||||
taskId,
|
||||
strength: scopeStrength,
|
||||
prompt: scopePrompt.trim() || undefined,
|
||||
options: { research: false }
|
||||
});
|
||||
|
||||
setScopePrompt('');
|
||||
refreshComplexityAfterAI();
|
||||
} catch (error) {
|
||||
console.error('❌ AIActionsSection: Failed to scope up task:', error);
|
||||
} finally {
|
||||
setLastAction(null);
|
||||
}
|
||||
};
|
||||
|
||||
const handleScopeDown = async () => {
|
||||
if (!currentTask) {
|
||||
return;
|
||||
}
|
||||
|
||||
setLastAction('scope-down');
|
||||
|
||||
try {
|
||||
const taskId = isSubtask && parentTask ? `${parentTask.id}.${currentTask.id}` : currentTask.id;
|
||||
|
||||
await scopeDownTask.mutateAsync({
|
||||
taskId,
|
||||
strength: scopeStrength,
|
||||
prompt: scopePrompt.trim() || undefined,
|
||||
options: { research: false }
|
||||
});
|
||||
|
||||
setScopePrompt('');
|
||||
refreshComplexityAfterAI();
|
||||
} catch (error) {
|
||||
console.error('❌ AIActionsSection: Failed to scope down task:', error);
|
||||
} finally {
|
||||
setLastAction(null);
|
||||
}
|
||||
};
|
||||
|
||||
// Track loading states based on the last action
|
||||
const isLoading = updateTask.isPending || updateSubtask.isPending;
|
||||
const isLoading = updateTask.isPending || updateSubtask.isPending || scopeUpTask.isPending || scopeDownTask.isPending;
|
||||
const isRegenerating = isLoading && lastAction === 'regenerate';
|
||||
const isAppending = isLoading && lastAction === 'append';
|
||||
const isScopingUp = isLoading && lastAction === 'scope-up';
|
||||
const isScopingDown = isLoading && lastAction === 'scope-down';
|
||||
|
||||
return (
|
||||
<CollapsibleSection
|
||||
@@ -115,73 +175,160 @@ export const AIActionsSection: React.FC<AIActionsSectionProps> = ({
|
||||
defaultExpanded={true}
|
||||
buttonClassName="text-vscode-foreground/80 hover:text-vscode-foreground"
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<Label
|
||||
htmlFor="ai-prompt"
|
||||
className="block text-sm font-medium text-vscode-foreground/80 mb-2"
|
||||
>
|
||||
Enter your prompt
|
||||
</Label>
|
||||
<Textarea
|
||||
id="ai-prompt"
|
||||
placeholder={
|
||||
isSubtask
|
||||
? 'Describe implementation notes, progress updates, or findings to add to this subtask...'
|
||||
: 'Describe what you want to change or add to this task...'
|
||||
}
|
||||
value={prompt}
|
||||
onChange={(e) => setPrompt(e.target.value)}
|
||||
className="min-h-[100px] bg-vscode-input-background border-vscode-input-border text-vscode-input-foreground placeholder-vscode-input-foreground/50 focus:border-vscode-focusBorder focus:ring-vscode-focusBorder"
|
||||
disabled={isRegenerating || isAppending}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-3">
|
||||
{!isSubtask && (
|
||||
<Button
|
||||
onClick={handleRegenerate}
|
||||
disabled={!prompt.trim() || isRegenerating || isAppending}
|
||||
className="bg-primary text-primary-foreground hover:bg-primary/90"
|
||||
<div className="space-y-6">
|
||||
{/* Standard AI Actions Section */}
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<Label
|
||||
htmlFor="ai-prompt"
|
||||
className="block text-sm font-medium text-vscode-foreground/80 mb-2"
|
||||
>
|
||||
{isRegenerating ? (
|
||||
Enter your prompt
|
||||
</Label>
|
||||
<Textarea
|
||||
id="ai-prompt"
|
||||
placeholder={
|
||||
isSubtask
|
||||
? 'Describe implementation notes, progress updates, or findings to add to this subtask...'
|
||||
: 'Describe what you want to change or add to this task...'
|
||||
}
|
||||
value={prompt}
|
||||
onChange={(e) => setPrompt(e.target.value)}
|
||||
className="min-h-[100px] bg-vscode-input-background border-vscode-input-border text-vscode-input-foreground placeholder-vscode-input-foreground/50 focus:border-vscode-focusBorder focus:ring-vscode-focusBorder"
|
||||
disabled={isLoading}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-3">
|
||||
{!isSubtask && (
|
||||
<Button
|
||||
onClick={handleRegenerate}
|
||||
disabled={!prompt.trim() || isLoading}
|
||||
className="bg-primary text-primary-foreground hover:bg-primary/90"
|
||||
>
|
||||
{isRegenerating ? (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
||||
Regenerating...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Wand2 className="w-4 h-4 mr-2" />
|
||||
Regenerate Task
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<Button
|
||||
onClick={handleAppend}
|
||||
disabled={!prompt.trim() || isLoading}
|
||||
variant={isSubtask ? 'default' : 'outline'}
|
||||
className={
|
||||
isSubtask
|
||||
? 'bg-primary text-primary-foreground hover:bg-primary/90'
|
||||
: 'bg-secondary text-secondary-foreground hover:bg-secondary/90 border-widget-border'
|
||||
}
|
||||
>
|
||||
{isAppending ? (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
||||
Regenerating...
|
||||
{isSubtask ? 'Updating...' : 'Appending...'}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Wand2 className="w-4 h-4 mr-2" />
|
||||
Regenerate Task
|
||||
<PlusCircle className="w-4 h-4 mr-2" />
|
||||
{isSubtask ? 'Add Notes to Subtask' : 'Append to Task'}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<Button
|
||||
onClick={handleAppend}
|
||||
disabled={!prompt.trim() || isRegenerating || isAppending}
|
||||
variant={isSubtask ? 'default' : 'outline'}
|
||||
className={
|
||||
isSubtask
|
||||
? 'bg-primary text-primary-foreground hover:bg-primary/90'
|
||||
: 'bg-secondary text-secondary-foreground hover:bg-secondary/90 border-widget-border'
|
||||
}
|
||||
>
|
||||
{isAppending ? (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
||||
{isSubtask ? 'Updating...' : 'Appending...'}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<PlusCircle className="w-4 h-4 mr-2" />
|
||||
{isSubtask ? 'Add Notes to Subtask' : 'Append to Task'}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Scope Adjustment Section */}
|
||||
<div className="border-t border-vscode-widget-border pt-4 space-y-4">
|
||||
<div>
|
||||
<Label className="block text-sm font-medium text-vscode-foreground/80 mb-3">
|
||||
Task Complexity Adjustment
|
||||
</Label>
|
||||
|
||||
{/* Strength Selection */}
|
||||
<div className="mb-3">
|
||||
<Label className="block text-xs text-vscode-foreground/60 mb-2">
|
||||
Adjustment Strength
|
||||
</Label>
|
||||
<div className="flex gap-2">
|
||||
{(['light', 'regular', 'heavy'] as const).map((strength) => (
|
||||
<Button
|
||||
key={strength}
|
||||
onClick={() => setScopeStrength(strength)}
|
||||
variant={scopeStrength === strength ? 'default' : 'outline'}
|
||||
size="sm"
|
||||
className={
|
||||
scopeStrength === strength
|
||||
? 'bg-accent text-accent-foreground border-accent'
|
||||
: 'border-widget-border text-vscode-foreground/80 hover:bg-vscode-list-hoverBackground'
|
||||
}
|
||||
disabled={isLoading}
|
||||
>
|
||||
{strength.charAt(0).toUpperCase() + strength.slice(1)}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Scope Prompt */}
|
||||
<Textarea
|
||||
placeholder="Optional: Specify how to adjust complexity (e.g., 'Focus on error handling', 'Remove unnecessary details', 'Add more implementation steps')"
|
||||
value={scopePrompt}
|
||||
onChange={(e) => setScopePrompt(e.target.value)}
|
||||
className="min-h-[80px] bg-vscode-input-background border-vscode-input-border text-vscode-input-foreground placeholder-vscode-input-foreground/50 focus:border-vscode-focusBorder focus:ring-vscode-focusBorder"
|
||||
disabled={isLoading}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-3">
|
||||
<Button
|
||||
onClick={handleScopeUp}
|
||||
disabled={isLoading}
|
||||
variant="outline"
|
||||
className="flex-1 border-green-600/50 text-green-400 hover:bg-green-600/10 hover:border-green-500"
|
||||
>
|
||||
{isScopingUp ? (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
||||
Scoping Up...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<TrendingUp className="w-4 h-4 mr-2" />
|
||||
Scope Up
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
onClick={handleScopeDown}
|
||||
disabled={isLoading}
|
||||
variant="outline"
|
||||
className="flex-1 border-blue-600/50 text-blue-400 hover:bg-blue-600/10 hover:border-blue-500"
|
||||
>
|
||||
{isScopingDown ? (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
||||
Scoping Down...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<TrendingDown className="w-4 h-4 mr-2" />
|
||||
Scope Down
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Help Text */}
|
||||
<div className="text-xs text-vscode-foreground/60 space-y-1">
|
||||
{isSubtask ? (
|
||||
<p>
|
||||
@@ -200,6 +347,12 @@ export const AIActionsSection: React.FC<AIActionsSectionProps> = ({
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
<p>
|
||||
<strong>Scope Up:</strong> Increases task complexity with more details, requirements, or implementation steps
|
||||
</p>
|
||||
<p>
|
||||
<strong>Scope Down:</strong> Decreases task complexity by simplifying or removing unnecessary details
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</CollapsibleSection>
|
||||
|
||||
@@ -227,3 +227,119 @@ export function useUpdateSubtask() {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Hook to scope up task complexity
|
||||
export function useScopeUpTask() {
|
||||
const { sendMessage } = useVSCodeContext();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async ({
|
||||
taskId,
|
||||
strength = 'regular',
|
||||
prompt,
|
||||
options = {}
|
||||
}: {
|
||||
taskId: string;
|
||||
strength?: 'light' | 'regular' | 'heavy';
|
||||
prompt?: string;
|
||||
options?: { research?: boolean };
|
||||
}) => {
|
||||
console.log('🔄 Scoping up task:', taskId, strength, prompt, options);
|
||||
|
||||
const response = await sendMessage({
|
||||
type: 'mcpRequest',
|
||||
tool: 'scope_up_task',
|
||||
params: {
|
||||
id: taskId,
|
||||
strength,
|
||||
prompt,
|
||||
research: options.research || false
|
||||
}
|
||||
});
|
||||
|
||||
console.log('📥 Scope up task response:', response);
|
||||
|
||||
// Check for error in response
|
||||
if (response && typeof response === 'object' && 'error' in response) {
|
||||
throw new Error(response.error || 'Failed to scope up task');
|
||||
}
|
||||
|
||||
return response;
|
||||
},
|
||||
onSuccess: async (data, variables) => {
|
||||
console.log(
|
||||
'✅ Task scope up successful, invalidating all task queries'
|
||||
);
|
||||
console.log('Task ID:', variables.taskId);
|
||||
|
||||
// Invalidate ALL task-related queries
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: taskKeys.all
|
||||
});
|
||||
|
||||
console.log(
|
||||
'🔄 All task queries invalidated for scoped up task:',
|
||||
variables.taskId
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Hook to scope down task complexity
|
||||
export function useScopeDownTask() {
|
||||
const { sendMessage } = useVSCodeContext();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async ({
|
||||
taskId,
|
||||
strength = 'regular',
|
||||
prompt,
|
||||
options = {}
|
||||
}: {
|
||||
taskId: string;
|
||||
strength?: 'light' | 'regular' | 'heavy';
|
||||
prompt?: string;
|
||||
options?: { research?: boolean };
|
||||
}) => {
|
||||
console.log('🔄 Scoping down task:', taskId, strength, prompt, options);
|
||||
|
||||
const response = await sendMessage({
|
||||
type: 'mcpRequest',
|
||||
tool: 'scope_down_task',
|
||||
params: {
|
||||
id: taskId,
|
||||
strength,
|
||||
prompt,
|
||||
research: options.research || false
|
||||
}
|
||||
});
|
||||
|
||||
console.log('📥 Scope down task response:', response);
|
||||
|
||||
// Check for error in response
|
||||
if (response && typeof response === 'object' && 'error' in response) {
|
||||
throw new Error(response.error || 'Failed to scope down task');
|
||||
}
|
||||
|
||||
return response;
|
||||
},
|
||||
onSuccess: async (data, variables) => {
|
||||
console.log(
|
||||
'✅ Task scope down successful, invalidating all task queries'
|
||||
);
|
||||
console.log('Task ID:', variables.taskId);
|
||||
|
||||
// Invalidate ALL task-related queries
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: taskKeys.all
|
||||
});
|
||||
|
||||
console.log(
|
||||
'🔄 All task queries invalidated for scoped down task:',
|
||||
variables.taskId
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
2
package-lock.json
generated
2
package-lock.json
generated
@@ -84,7 +84,7 @@
|
||||
}
|
||||
},
|
||||
"apps/extension": {
|
||||
"version": "0.0.0",
|
||||
"version": "0.22.3",
|
||||
"devDependencies": {
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
"@dnd-kit/modifiers": "^9.0.0",
|
||||
|
||||
Reference in New Issue
Block a user