mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-07 08:26:51 +00:00
feat: implement automatic TODO list creation for complex tasks (#5470)
- Add TaskComplexityAnalyzer module to analyze task complexity using keyword analysis, pattern matching, and structural analysis - Integrate automatic TODO creation into Task.startTask() method without manual user intervention - Generate contextual TODO items based on task type (API development, UI components, bug fixes, etc.) - Add comprehensive test coverage with 12 passing tests - Maintain backward compatibility with existing TODO list functionality - Follow existing codebase patterns and TypeScript best practices Fixes #5470
This commit is contained in:
parent
6ab57fae43
commit
37e0eac77e
4 changed files with 846 additions and 0 deletions
|
|
@ -87,6 +87,7 @@ import { ApiMessage } from "../task-persistence/apiMessages"
|
|||
import { getMessagesSinceLastSummary, summarizeConversation } from "../condense"
|
||||
import { maybeRemoveImageBlocks } from "../../api/transform/image-cleaning"
|
||||
import { restoreTodoListForTask } from "../tools/updateTodoListTool"
|
||||
import { TaskComplexityAnalyzer, DEFAULT_AUTO_TODO_CONFIG } from "./TaskComplexityAnalyzer"
|
||||
|
||||
// Constants
|
||||
const MAX_EXPONENTIAL_BACKOFF_SECONDS = 600 // 10 minutes
|
||||
|
|
@ -743,6 +744,32 @@ export class Task extends EventEmitter<ClineEvents> {
|
|||
await this.say("text", task, images)
|
||||
this.isInitialized = true
|
||||
|
||||
// Check if we should automatically create a TODO list for this task
|
||||
if (task) {
|
||||
try {
|
||||
const analyzer = new TaskComplexityAnalyzer(DEFAULT_AUTO_TODO_CONFIG)
|
||||
const analysis = analyzer.analyzeTask(task)
|
||||
|
||||
if (analysis.shouldCreateTodos && analysis.suggestedTodos.length > 0) {
|
||||
// Create automatic TODO list without user intervention
|
||||
this.todoList = analysis.suggestedTodos.map((todo, index) => ({
|
||||
id: `auto-${index + 1}`,
|
||||
content: todo,
|
||||
status: "pending" as const,
|
||||
}))
|
||||
|
||||
// Notify user that automatic TODO list was created
|
||||
await this.say(
|
||||
"text",
|
||||
`📋 **Automatic TODO List Created**\n\nBased on the task complexity, I've created a TODO list to help track progress:\n\n${analysis.suggestedTodos.map((todo, i) => `${i + 1}. ${todo}`).join("\n")}\n\nI'll update this list as I work through the task.`,
|
||||
)
|
||||
}
|
||||
} catch (error) {
|
||||
// Don't let TODO creation errors interrupt task execution
|
||||
console.warn("Failed to create automatic TODO list:", error)
|
||||
}
|
||||
}
|
||||
|
||||
let imageBlocks: Anthropic.ImageBlockParam[] = formatResponse.imageBlocks(images)
|
||||
|
||||
console.log(`[subtasks] task ${this.taskId}.${this.instanceId} starting`)
|
||||
|
|
|
|||
391
src/core/task/TaskComplexityAnalyzer.ts
Normal file
391
src/core/task/TaskComplexityAnalyzer.ts
Normal file
|
|
@ -0,0 +1,391 @@
|
|||
import { TodoItem } from "@roo-code/types"
|
||||
import { addTodoToTask } from "../tools/updateTodoListTool"
|
||||
import { Task } from "./Task"
|
||||
|
||||
/**
|
||||
* Configuration for automatic TODO list creation
|
||||
*/
|
||||
export interface AutoTodoConfig {
|
||||
enabled: boolean
|
||||
complexityThreshold: number
|
||||
minTaskLength: number
|
||||
maxInitialTodos: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Default configuration for automatic TODO list creation
|
||||
*/
|
||||
export const DEFAULT_AUTO_TODO_CONFIG: AutoTodoConfig = {
|
||||
enabled: true,
|
||||
complexityThreshold: 3, // Minimum complexity score to trigger auto TODO
|
||||
minTaskLength: 50, // Minimum task description length
|
||||
maxInitialTodos: 8, // Maximum number of initial TODOs to create
|
||||
}
|
||||
|
||||
/**
|
||||
* Keywords that indicate task complexity and suggest multiple steps
|
||||
*/
|
||||
const COMPLEXITY_KEYWORDS = {
|
||||
// High complexity indicators (weight: 3)
|
||||
high: [
|
||||
"create",
|
||||
"build",
|
||||
"implement",
|
||||
"develop",
|
||||
"design",
|
||||
"architect",
|
||||
"full-stack",
|
||||
"end-to-end",
|
||||
"complete",
|
||||
"entire",
|
||||
"comprehensive",
|
||||
"system",
|
||||
"application",
|
||||
"project",
|
||||
"framework",
|
||||
"platform",
|
||||
"integrate",
|
||||
"configure",
|
||||
"setup",
|
||||
"install",
|
||||
"deploy",
|
||||
],
|
||||
// Medium complexity indicators (weight: 2)
|
||||
medium: [
|
||||
"add",
|
||||
"update",
|
||||
"modify",
|
||||
"refactor",
|
||||
"optimize",
|
||||
"improve",
|
||||
"enhance",
|
||||
"extend",
|
||||
"migrate",
|
||||
"convert",
|
||||
"transform",
|
||||
"test",
|
||||
"debug",
|
||||
"fix",
|
||||
"resolve",
|
||||
"handle",
|
||||
"manage",
|
||||
],
|
||||
// Step indicators (weight: 2)
|
||||
steps: [
|
||||
"step",
|
||||
"steps",
|
||||
"phase",
|
||||
"phases",
|
||||
"stage",
|
||||
"stages",
|
||||
"first",
|
||||
"then",
|
||||
"next",
|
||||
"after",
|
||||
"before",
|
||||
"finally",
|
||||
"initially",
|
||||
"subsequently",
|
||||
"process",
|
||||
"workflow",
|
||||
],
|
||||
// Multiple item indicators (weight: 1)
|
||||
multiple: [
|
||||
"multiple",
|
||||
"several",
|
||||
"various",
|
||||
"different",
|
||||
"many",
|
||||
"all",
|
||||
"each",
|
||||
"every",
|
||||
"both",
|
||||
"and",
|
||||
"also",
|
||||
"plus",
|
||||
],
|
||||
}
|
||||
|
||||
/**
|
||||
* Patterns that suggest multiple steps or complex workflows
|
||||
*/
|
||||
const COMPLEXITY_PATTERNS = [
|
||||
// Lists or enumerations
|
||||
/\d+\.\s+/g, // "1. ", "2. ", etc.
|
||||
/[-*]\s+/g, // "- " or "* " bullet points
|
||||
/\b(first|second|third|fourth|fifth|then|next|after|finally)\b/gi,
|
||||
// Conditional or branching logic
|
||||
/\b(if|when|unless|depending|based on|according to)\b/gi,
|
||||
// Multiple technologies or components
|
||||
/\b(with|using|and|plus|\+|&)\s+\w+/gi,
|
||||
// Authentication, database, API patterns
|
||||
/\b(auth|database|api|frontend|backend|server|client)\b/gi,
|
||||
]
|
||||
|
||||
/**
|
||||
* Analyzes task description to determine complexity and suggest initial TODOs
|
||||
*/
|
||||
export class TaskComplexityAnalyzer {
|
||||
private config: AutoTodoConfig
|
||||
|
||||
constructor(config: Partial<AutoTodoConfig> = {}) {
|
||||
this.config = { ...DEFAULT_AUTO_TODO_CONFIG, ...config }
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyzes a task description and returns complexity score and suggested TODOs
|
||||
*/
|
||||
analyzeTask(taskDescription: string): {
|
||||
complexityScore: number
|
||||
shouldCreateTodos: boolean
|
||||
suggestedTodos: string[]
|
||||
reasoning: string[]
|
||||
} {
|
||||
if (!taskDescription || taskDescription.length < this.config.minTaskLength) {
|
||||
return {
|
||||
complexityScore: 0,
|
||||
shouldCreateTodos: false,
|
||||
suggestedTodos: [],
|
||||
reasoning: ["Task description too short for complexity analysis"],
|
||||
}
|
||||
}
|
||||
|
||||
const reasoning: string[] = []
|
||||
let complexityScore = 0
|
||||
|
||||
// Analyze keyword complexity
|
||||
const keywordScore = this.analyzeKeywords(taskDescription, reasoning)
|
||||
complexityScore += keywordScore
|
||||
|
||||
// Analyze pattern complexity
|
||||
const patternScore = this.analyzePatterns(taskDescription, reasoning)
|
||||
complexityScore += patternScore
|
||||
|
||||
// Analyze length and structure
|
||||
const structureScore = this.analyzeStructure(taskDescription, reasoning)
|
||||
complexityScore += structureScore
|
||||
|
||||
// Generate suggested TODOs based on the task
|
||||
const suggestedTodos = this.generateSuggestedTodos(taskDescription, complexityScore)
|
||||
|
||||
const shouldCreateTodos =
|
||||
this.config.enabled && complexityScore >= this.config.complexityThreshold && suggestedTodos.length > 0
|
||||
|
||||
reasoning.push(`Total complexity score: ${complexityScore}`)
|
||||
reasoning.push(`Threshold: ${this.config.complexityThreshold}`)
|
||||
reasoning.push(`Should create TODOs: ${shouldCreateTodos}`)
|
||||
|
||||
return {
|
||||
complexityScore,
|
||||
shouldCreateTodos,
|
||||
suggestedTodos,
|
||||
reasoning,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyzes keywords in the task description
|
||||
*/
|
||||
private analyzeKeywords(task: string, reasoning: string[]): number {
|
||||
const lowerTask = task.toLowerCase()
|
||||
let score = 0
|
||||
|
||||
// Check high complexity keywords
|
||||
const highMatches = COMPLEXITY_KEYWORDS.high.filter((keyword) => lowerTask.includes(keyword))
|
||||
if (highMatches.length > 0) {
|
||||
score += highMatches.length * 3
|
||||
reasoning.push(`High complexity keywords found: ${highMatches.join(", ")} (+${highMatches.length * 3})`)
|
||||
}
|
||||
|
||||
// Check medium complexity keywords
|
||||
const mediumMatches = COMPLEXITY_KEYWORDS.medium.filter((keyword) => lowerTask.includes(keyword))
|
||||
if (mediumMatches.length > 0) {
|
||||
score += mediumMatches.length * 2
|
||||
reasoning.push(
|
||||
`Medium complexity keywords found: ${mediumMatches.join(", ")} (+${mediumMatches.length * 2})`,
|
||||
)
|
||||
}
|
||||
|
||||
// Check step indicators
|
||||
const stepMatches = COMPLEXITY_KEYWORDS.steps.filter((keyword) => lowerTask.includes(keyword))
|
||||
if (stepMatches.length > 0) {
|
||||
score += stepMatches.length * 2
|
||||
reasoning.push(`Step indicators found: ${stepMatches.join(", ")} (+${stepMatches.length * 2})`)
|
||||
}
|
||||
|
||||
// Check multiple item indicators
|
||||
const multipleMatches = COMPLEXITY_KEYWORDS.multiple.filter((keyword) => lowerTask.includes(keyword))
|
||||
if (multipleMatches.length > 0) {
|
||||
score += multipleMatches.length * 1
|
||||
reasoning.push(`Multiple item indicators found: ${multipleMatches.join(", ")} (+${multipleMatches.length})`)
|
||||
}
|
||||
|
||||
return score
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyzes patterns that suggest complexity
|
||||
*/
|
||||
private analyzePatterns(task: string, reasoning: string[]): number {
|
||||
let score = 0
|
||||
|
||||
for (const pattern of COMPLEXITY_PATTERNS) {
|
||||
const matches = task.match(pattern)
|
||||
if (matches && matches.length > 0) {
|
||||
score += matches.length
|
||||
reasoning.push(`Pattern matches for ${pattern.source}: ${matches.length} (+${matches.length})`)
|
||||
}
|
||||
}
|
||||
|
||||
return Math.min(score, 10) // Cap pattern score at 10
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyzes task structure and length
|
||||
*/
|
||||
private analyzeStructure(task: string, reasoning: string[]): number {
|
||||
let score = 0
|
||||
|
||||
// Length-based scoring
|
||||
if (task.length > 200) {
|
||||
score += 2
|
||||
reasoning.push("Long task description (+2)")
|
||||
} else if (task.length > 100) {
|
||||
score += 1
|
||||
reasoning.push("Medium task description (+1)")
|
||||
}
|
||||
|
||||
// Sentence count
|
||||
const sentences = task.split(/[.!?]+/).filter((s) => s.trim().length > 0)
|
||||
if (sentences.length > 3) {
|
||||
score += 2
|
||||
reasoning.push(`Multiple sentences: ${sentences.length} (+2)`)
|
||||
}
|
||||
|
||||
// Line breaks suggest structured content
|
||||
const lines = task.split(/\n/).filter((l) => l.trim().length > 0)
|
||||
if (lines.length > 2) {
|
||||
score += 1
|
||||
reasoning.push(`Multiple lines: ${lines.length} (+1)`)
|
||||
}
|
||||
|
||||
return score
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates suggested TODO items based on task analysis
|
||||
*/
|
||||
private generateSuggestedTodos(task: string, complexityScore: number): string[] {
|
||||
const todos: string[] = []
|
||||
const lowerTask = task.toLowerCase()
|
||||
|
||||
// Common patterns for different types of tasks
|
||||
if (this.containsAny(lowerTask, ["create", "build", "develop", "implement"])) {
|
||||
if (this.containsAny(lowerTask, ["app", "application", "website", "system"])) {
|
||||
todos.push("Analyze requirements and define scope")
|
||||
todos.push("Design system architecture")
|
||||
todos.push("Set up project structure")
|
||||
todos.push("Implement core functionality")
|
||||
todos.push("Add error handling and validation")
|
||||
todos.push("Write tests")
|
||||
todos.push("Update documentation")
|
||||
} else if (this.containsAny(lowerTask, ["api", "endpoint", "service"])) {
|
||||
todos.push("Define API specification")
|
||||
todos.push("Set up routing and middleware")
|
||||
todos.push("Implement endpoint logic")
|
||||
todos.push("Add authentication and authorization")
|
||||
todos.push("Write API tests")
|
||||
todos.push("Update API documentation")
|
||||
} else if (this.containsAny(lowerTask, ["component", "ui", "interface"])) {
|
||||
todos.push("Design component interface")
|
||||
todos.push("Implement component logic")
|
||||
todos.push("Add styling and responsive design")
|
||||
todos.push("Handle user interactions")
|
||||
todos.push("Write component tests")
|
||||
todos.push("Update component documentation")
|
||||
}
|
||||
}
|
||||
|
||||
if (this.containsAny(lowerTask, ["fix", "debug", "resolve", "issue"])) {
|
||||
todos.push("Reproduce the issue")
|
||||
todos.push("Identify root cause")
|
||||
todos.push("Implement fix")
|
||||
todos.push("Test the solution")
|
||||
todos.push("Verify no regression")
|
||||
}
|
||||
|
||||
if (this.containsAny(lowerTask, ["refactor", "optimize", "improve"])) {
|
||||
todos.push("Analyze current implementation")
|
||||
todos.push("Identify improvement opportunities")
|
||||
todos.push("Plan refactoring approach")
|
||||
todos.push("Implement improvements")
|
||||
todos.push("Test refactored code")
|
||||
todos.push("Update related documentation")
|
||||
}
|
||||
|
||||
if (this.containsAny(lowerTask, ["setup", "configure", "install"])) {
|
||||
todos.push("Review setup requirements")
|
||||
todos.push("Install dependencies")
|
||||
todos.push("Configure environment")
|
||||
todos.push("Test configuration")
|
||||
todos.push("Document setup process")
|
||||
}
|
||||
|
||||
// If no specific patterns matched, create generic todos based on complexity
|
||||
if (todos.length === 0 && complexityScore >= this.config.complexityThreshold) {
|
||||
todos.push("Break down the task into smaller steps")
|
||||
todos.push("Analyze requirements and constraints")
|
||||
todos.push("Plan implementation approach")
|
||||
todos.push("Execute the main task")
|
||||
todos.push("Test and verify results")
|
||||
}
|
||||
|
||||
// Limit the number of initial todos
|
||||
return todos.slice(0, this.config.maxInitialTodos)
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to check if text contains any of the given keywords
|
||||
*/
|
||||
private containsAny(text: string, keywords: string[]): boolean {
|
||||
return keywords.some((keyword) => text.includes(keyword))
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates automatic TODO list for a task if complexity threshold is met
|
||||
*/
|
||||
async createAutomaticTodoList(cline: Task, taskDescription: string): Promise<boolean> {
|
||||
const analysis = this.analyzeTask(taskDescription)
|
||||
|
||||
if (!analysis.shouldCreateTodos) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Create TODO items
|
||||
for (const todoContent of analysis.suggestedTodos) {
|
||||
addTodoToTask(cline, todoContent, "pending")
|
||||
}
|
||||
|
||||
// Log the automatic TODO creation
|
||||
await cline.say(
|
||||
"text",
|
||||
`🎯 **Automatic TODO List Created**\n\nDetected complex task requiring step-by-step tracking. Created ${analysis.suggestedTodos.length} initial TODO items:\n\n${analysis.suggestedTodos.map((todo, i) => `${i + 1}. ${todo}`).join("\n")}\n\n*You can update this list as the task progresses using the update_todo_list tool.*`,
|
||||
)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the configuration
|
||||
*/
|
||||
updateConfig(newConfig: Partial<AutoTodoConfig>): void {
|
||||
this.config = { ...this.config, ...newConfig }
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current configuration
|
||||
*/
|
||||
getConfig(): AutoTodoConfig {
|
||||
return { ...this.config }
|
||||
}
|
||||
}
|
||||
229
src/tests/task/Task-AutoTodo.test.ts
Normal file
229
src/tests/task/Task-AutoTodo.test.ts
Normal file
|
|
@ -0,0 +1,229 @@
|
|||
import { describe, test, expect, vi, beforeEach } from "vitest"
|
||||
import { Task } from "../../core/task/Task"
|
||||
import { ClineProvider } from "../../core/webview/ClineProvider"
|
||||
|
||||
// Mock the ClineProvider and its dependencies
|
||||
vi.mock("../../core/webview/ClineProvider")
|
||||
vi.mock("vscode", () => ({
|
||||
workspace: {
|
||||
workspaceFolders: [],
|
||||
onDidChangeWorkspaceFolders: vi.fn(),
|
||||
createFileSystemWatcher: vi.fn(() => ({
|
||||
dispose: vi.fn(),
|
||||
onDidChange: vi.fn(),
|
||||
onDidCreate: vi.fn(),
|
||||
onDidDelete: vi.fn(),
|
||||
})),
|
||||
},
|
||||
window: {
|
||||
createTextEditorDecorationType: vi.fn(() => ({
|
||||
dispose: vi.fn(),
|
||||
})),
|
||||
showInformationMessage: vi.fn(),
|
||||
showErrorMessage: vi.fn(),
|
||||
showWarningMessage: vi.fn(),
|
||||
createWebviewPanel: vi.fn(),
|
||||
registerWebviewViewProvider: vi.fn(),
|
||||
onDidChangeActiveTextEditor: vi.fn(),
|
||||
activeTextEditor: undefined,
|
||||
},
|
||||
Uri: {
|
||||
file: vi.fn((path) => ({ fsPath: path, path })),
|
||||
},
|
||||
RelativePattern: vi.fn(),
|
||||
FileSystemWatcher: vi.fn(),
|
||||
EventEmitter: vi.fn(() => ({
|
||||
event: vi.fn(),
|
||||
fire: vi.fn(),
|
||||
dispose: vi.fn(),
|
||||
})),
|
||||
ExtensionContext: vi.fn(),
|
||||
ViewColumn: {
|
||||
One: 1,
|
||||
Two: 2,
|
||||
Three: 3,
|
||||
},
|
||||
TextEditorRevealType: {
|
||||
Default: 0,
|
||||
InCenter: 1,
|
||||
InCenterIfOutsideViewport: 2,
|
||||
},
|
||||
}))
|
||||
|
||||
// Mock other dependencies
|
||||
vi.mock("../../services/mcp/McpServerManager", () => ({
|
||||
McpServerManager: {
|
||||
getInstance: vi.fn(() => Promise.resolve(null)),
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock("../../core/prompts/system", () => ({
|
||||
SYSTEM_PROMPT: vi.fn(() => "test system prompt"),
|
||||
}))
|
||||
|
||||
vi.mock("../../core/environment/getEnvironmentDetails", () => ({
|
||||
getEnvironmentDetails: vi.fn(() => Promise.resolve("test environment")),
|
||||
}))
|
||||
|
||||
vi.mock("@roo-code/telemetry", () => ({
|
||||
TelemetryService: {
|
||||
instance: {
|
||||
captureTaskCreated: vi.fn(),
|
||||
captureTaskRestarted: vi.fn(),
|
||||
captureConversationMessage: vi.fn(),
|
||||
captureLlmCompletion: vi.fn(),
|
||||
captureConsecutiveMistakeError: vi.fn(),
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock("@roo-code/cloud", () => ({
|
||||
CloudService: {
|
||||
isEnabled: vi.fn(() => false),
|
||||
instance: {
|
||||
captureEvent: vi.fn(),
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
||||
describe("Task Auto TODO Integration", () => {
|
||||
let mockProvider: any
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
|
||||
mockProvider = {
|
||||
context: {
|
||||
globalStorageUri: { fsPath: "/tmp/test" },
|
||||
subscriptions: [],
|
||||
},
|
||||
getState: vi.fn(() =>
|
||||
Promise.resolve({
|
||||
mode: "code",
|
||||
experiments: {},
|
||||
browserViewportSize: "1024x768",
|
||||
}),
|
||||
),
|
||||
postStateToWebview: vi.fn(),
|
||||
postMessageToWebview: vi.fn(),
|
||||
updateTaskHistory: vi.fn(),
|
||||
log: vi.fn(),
|
||||
providerSettingsManager: {
|
||||
getProfile: vi.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
test("should create automatic TODO list for complex tasks", async () => {
|
||||
const complexTask = `
|
||||
Create a comprehensive web application with the following features:
|
||||
1. User authentication system with login and registration
|
||||
2. Dashboard with real-time analytics
|
||||
3. Settings page with user preferences
|
||||
4. API integration with external services
|
||||
5. Mobile responsive design
|
||||
6. Comprehensive testing suite
|
||||
`
|
||||
|
||||
// Mock the say method to capture TODO creation message
|
||||
const sayMock = vi.fn()
|
||||
|
||||
// Use Task.create to properly start the task
|
||||
const [task, taskPromise] = Task.create({
|
||||
provider: mockProvider as ClineProvider,
|
||||
apiConfiguration: {
|
||||
apiProvider: "anthropic",
|
||||
apiKey: "test-key",
|
||||
apiModelId: "claude-3-sonnet-20240229",
|
||||
},
|
||||
task: complexTask,
|
||||
})
|
||||
|
||||
// Replace the say method after creation but before task starts
|
||||
task.say = sayMock
|
||||
|
||||
// Wait for task to start (this will trigger the automatic TODO creation)
|
||||
await taskPromise.catch(() => {
|
||||
// Ignore errors from the task execution since we're just testing TODO creation
|
||||
})
|
||||
|
||||
// Verify that a TODO list was created
|
||||
expect(task.todoList).toBeDefined()
|
||||
expect(task.todoList!.length).toBeGreaterThan(0)
|
||||
|
||||
// Verify that all TODO items have the correct structure
|
||||
task.todoList!.forEach((todo) => {
|
||||
expect(todo).toHaveProperty("id")
|
||||
expect(todo).toHaveProperty("content")
|
||||
expect(todo).toHaveProperty("status")
|
||||
expect(todo.status).toBe("pending")
|
||||
})
|
||||
|
||||
// Verify that a notification message was sent about TODO creation
|
||||
expect(sayMock).toHaveBeenCalledWith("text", expect.stringContaining("Automatic TODO List Created"))
|
||||
})
|
||||
|
||||
test("should not create TODO list for simple tasks", async () => {
|
||||
const simpleTask = "Fix the typo in README.md"
|
||||
|
||||
// Mock the say method to capture TODO creation message
|
||||
const sayMock = vi.fn()
|
||||
|
||||
// Use Task.create to properly start the task
|
||||
const [task, taskPromise] = Task.create({
|
||||
provider: mockProvider as ClineProvider,
|
||||
apiConfiguration: {
|
||||
apiProvider: "anthropic",
|
||||
apiKey: "test-key",
|
||||
apiModelId: "claude-3-sonnet-20240229",
|
||||
},
|
||||
task: simpleTask,
|
||||
})
|
||||
|
||||
// Replace the say method after creation but before task starts
|
||||
task.say = sayMock
|
||||
|
||||
// Wait for task to start
|
||||
await taskPromise.catch(() => {
|
||||
// Ignore errors from the task execution since we're just testing TODO creation
|
||||
})
|
||||
|
||||
// Verify that no TODO list was created
|
||||
expect(task.todoList).toBeUndefined()
|
||||
|
||||
// Verify that no TODO creation message was sent
|
||||
expect(sayMock).not.toHaveBeenCalledWith("text", expect.stringContaining("Automatic TODO List Created"))
|
||||
})
|
||||
|
||||
test("should handle TODO creation errors gracefully", async () => {
|
||||
const complexTask = "Create a comprehensive application with multiple features and integrations"
|
||||
|
||||
// Mock console.warn to capture error handling
|
||||
const warnMock = vi.spyOn(console, "warn").mockImplementation(() => {})
|
||||
|
||||
// Mock the say method to capture TODO creation message
|
||||
const sayMock = vi.fn()
|
||||
|
||||
// Use Task.create to properly start the task
|
||||
const [task, taskPromise] = Task.create({
|
||||
provider: mockProvider as ClineProvider,
|
||||
apiConfiguration: {
|
||||
apiProvider: "anthropic",
|
||||
apiKey: "test-key",
|
||||
apiModelId: "claude-3-sonnet-20240229",
|
||||
},
|
||||
task: complexTask,
|
||||
})
|
||||
|
||||
// Replace the say method after creation but before task starts
|
||||
task.say = sayMock
|
||||
|
||||
// Should not throw an error even if TODO creation fails
|
||||
await expect(taskPromise).rejects.toThrow() // Task will fail due to mocking, but that's expected
|
||||
|
||||
// The task should still be created even if it fails later
|
||||
expect(task).toBeDefined()
|
||||
|
||||
warnMock.mockRestore()
|
||||
})
|
||||
})
|
||||
199
src/tests/task/TaskComplexityAnalyzer.test.ts
Normal file
199
src/tests/task/TaskComplexityAnalyzer.test.ts
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
import { describe, test, expect } from "vitest"
|
||||
import { TaskComplexityAnalyzer, DEFAULT_AUTO_TODO_CONFIG } from "../../core/task/TaskComplexityAnalyzer"
|
||||
|
||||
describe("TaskComplexityAnalyzer", () => {
|
||||
test("should detect simple tasks and not create TODOs", () => {
|
||||
const analyzer = new TaskComplexityAnalyzer(DEFAULT_AUTO_TODO_CONFIG)
|
||||
const result = analyzer.analyzeTask("Fix the typo in the README file")
|
||||
|
||||
expect(result.shouldCreateTodos).toBe(false)
|
||||
expect(result.complexityScore).toBeLessThan(DEFAULT_AUTO_TODO_CONFIG.complexityThreshold)
|
||||
expect(result.suggestedTodos).toHaveLength(0)
|
||||
})
|
||||
|
||||
test("should detect complex tasks and create TODOs", () => {
|
||||
const analyzer = new TaskComplexityAnalyzer(DEFAULT_AUTO_TODO_CONFIG)
|
||||
const complexTask = `
|
||||
Create a new React component for user authentication that includes:
|
||||
1. Login form with email and password validation
|
||||
2. Registration form with email verification
|
||||
3. Password reset functionality
|
||||
4. Integration with backend API
|
||||
5. Error handling and loading states
|
||||
6. Unit tests for all components
|
||||
7. Documentation and examples
|
||||
`
|
||||
|
||||
const result = analyzer.analyzeTask(complexTask)
|
||||
|
||||
expect(result.shouldCreateTodos).toBe(true)
|
||||
expect(result.complexityScore).toBeGreaterThanOrEqual(DEFAULT_AUTO_TODO_CONFIG.complexityThreshold)
|
||||
expect(result.suggestedTodos.length).toBeGreaterThan(0)
|
||||
expect(result.reasoning.length).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
test("should detect multi-step tasks", () => {
|
||||
const analyzer = new TaskComplexityAnalyzer(DEFAULT_AUTO_TODO_CONFIG)
|
||||
const multiStepTask = `
|
||||
First, analyze the existing codebase structure.
|
||||
Then, implement the new feature.
|
||||
Next, write comprehensive tests.
|
||||
Finally, update the documentation.
|
||||
`
|
||||
|
||||
const result = analyzer.analyzeTask(multiStepTask)
|
||||
|
||||
expect(result.shouldCreateTodos).toBe(true)
|
||||
expect(result.complexityScore).toBeGreaterThanOrEqual(DEFAULT_AUTO_TODO_CONFIG.complexityThreshold)
|
||||
expect(result.suggestedTodos.length).toBeGreaterThan(0)
|
||||
// The analyzer generates contextual TODOs based on keywords, not exact text
|
||||
expect(result.suggestedTodos.some((todo) => todo.toLowerCase().includes("implement"))).toBe(true)
|
||||
})
|
||||
|
||||
test("should detect numbered lists", () => {
|
||||
const analyzer = new TaskComplexityAnalyzer(DEFAULT_AUTO_TODO_CONFIG)
|
||||
const numberedTask = `
|
||||
Please complete the following tasks:
|
||||
1. Set up the development environment
|
||||
2. Create the database schema
|
||||
3. Implement the API endpoints
|
||||
4. Build the frontend interface
|
||||
5. Deploy to staging environment
|
||||
`
|
||||
|
||||
const result = analyzer.analyzeTask(numberedTask)
|
||||
|
||||
expect(result.shouldCreateTodos).toBe(true)
|
||||
expect(result.complexityScore).toBeGreaterThanOrEqual(DEFAULT_AUTO_TODO_CONFIG.complexityThreshold)
|
||||
expect(result.suggestedTodos.length).toBeGreaterThan(0)
|
||||
// The analyzer detects "implement" and "api" keywords and generates contextual TODOs
|
||||
expect(result.suggestedTodos.some((todo) => todo.toLowerCase().includes("api"))).toBe(true)
|
||||
})
|
||||
|
||||
test("should detect bullet point lists", () => {
|
||||
const analyzer = new TaskComplexityAnalyzer(DEFAULT_AUTO_TODO_CONFIG)
|
||||
const bulletTask = `
|
||||
Create a new feature that includes:
|
||||
- User authentication system
|
||||
- Dashboard with analytics
|
||||
- Settings page with preferences
|
||||
- Email notification system
|
||||
- Mobile responsive design
|
||||
`
|
||||
|
||||
const result = analyzer.analyzeTask(bulletTask)
|
||||
|
||||
expect(result.shouldCreateTodos).toBe(true)
|
||||
expect(result.complexityScore).toBeGreaterThanOrEqual(DEFAULT_AUTO_TODO_CONFIG.complexityThreshold)
|
||||
expect(result.suggestedTodos.length).toBeGreaterThan(0)
|
||||
// The analyzer detects "create" keyword and generates contextual TODOs for applications
|
||||
expect(
|
||||
result.suggestedTodos.some(
|
||||
(todo) =>
|
||||
todo.toLowerCase().includes("implement") ||
|
||||
todo.toLowerCase().includes("design") ||
|
||||
todo.toLowerCase().includes("analyze"),
|
||||
),
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
test("should handle custom configuration", () => {
|
||||
const customConfig = {
|
||||
enabled: true,
|
||||
complexityThreshold: 10,
|
||||
minTaskLength: 20,
|
||||
maxInitialTodos: 3,
|
||||
}
|
||||
|
||||
const analyzer = new TaskComplexityAnalyzer(customConfig)
|
||||
const task = "Implement a new feature and create comprehensive tests"
|
||||
|
||||
const result = analyzer.analyzeTask(task)
|
||||
|
||||
expect(result.complexityScore).toBeGreaterThan(0)
|
||||
expect(result.suggestedTodos.length).toBeLessThanOrEqual(customConfig.maxInitialTodos)
|
||||
})
|
||||
|
||||
test("should respect enabled setting", () => {
|
||||
const disabledConfig = {
|
||||
...DEFAULT_AUTO_TODO_CONFIG,
|
||||
enabled: false,
|
||||
}
|
||||
|
||||
const analyzer = new TaskComplexityAnalyzer(disabledConfig)
|
||||
const complexTask = `
|
||||
Create a comprehensive web application with:
|
||||
1. Frontend React components
|
||||
2. Backend API with authentication
|
||||
3. Database design and implementation
|
||||
4. Testing suite
|
||||
5. Documentation
|
||||
`
|
||||
|
||||
const result = analyzer.analyzeTask(complexTask)
|
||||
|
||||
expect(result.shouldCreateTodos).toBe(false)
|
||||
// TODOs may still be generated for analysis, but shouldCreateTodos should be false
|
||||
expect(result.complexityScore).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
test("should generate contextual TODOs based on task type", () => {
|
||||
const analyzer = new TaskComplexityAnalyzer(DEFAULT_AUTO_TODO_CONFIG)
|
||||
|
||||
// Bug fix task
|
||||
const bugTask = "Fix the authentication bug where users cannot log in with special characters in their password"
|
||||
const bugResult = analyzer.analyzeTask(bugTask)
|
||||
|
||||
if (bugResult.shouldCreateTodos) {
|
||||
expect(
|
||||
bugResult.suggestedTodos.some(
|
||||
(todo) =>
|
||||
todo.toLowerCase().includes("reproduce") ||
|
||||
todo.toLowerCase().includes("identify") ||
|
||||
todo.toLowerCase().includes("test"),
|
||||
),
|
||||
).toBe(true)
|
||||
}
|
||||
|
||||
// Feature task
|
||||
const featureTask = "Implement a new user dashboard with analytics, charts, and user management capabilities"
|
||||
const featureResult = analyzer.analyzeTask(featureTask)
|
||||
|
||||
if (featureResult.shouldCreateTodos) {
|
||||
expect(
|
||||
featureResult.suggestedTodos.some(
|
||||
(todo) =>
|
||||
todo.toLowerCase().includes("design") ||
|
||||
todo.toLowerCase().includes("implement") ||
|
||||
todo.toLowerCase().includes("test"),
|
||||
),
|
||||
).toBe(true)
|
||||
}
|
||||
})
|
||||
|
||||
test("should limit number of suggested TODOs", () => {
|
||||
const analyzer = new TaskComplexityAnalyzer(DEFAULT_AUTO_TODO_CONFIG)
|
||||
const longTask = `
|
||||
Create a massive application with:
|
||||
1. User authentication
|
||||
2. User profiles
|
||||
3. Dashboard
|
||||
4. Analytics
|
||||
5. Reporting
|
||||
6. Settings
|
||||
7. Admin panel
|
||||
8. API documentation
|
||||
9. Testing suite
|
||||
10. Deployment scripts
|
||||
11. Monitoring
|
||||
12. Logging
|
||||
13. Error handling
|
||||
14. Performance optimization
|
||||
15. Security audit
|
||||
`
|
||||
|
||||
const result = analyzer.analyzeTask(longTask)
|
||||
|
||||
expect(result.suggestedTodos.length).toBeLessThanOrEqual(DEFAULT_AUTO_TODO_CONFIG.maxInitialTodos)
|
||||
})
|
||||
})
|
||||
Loading…
Add table
Reference in a new issue