mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-05 01:50:27 +08:00
feat(lite-execute): intelligent task grouping with parallel/sequential execution
Major improvements: - Implement dependency-aware task grouping algorithm * Same file modifications → sequential batch * Different files + no dependencies → parallel batch * Keyword-based dependency inference (use/integrate/call/import) * Batch limits: Agent 7 tasks, CLI 4 tasks - Add parallel execution strategy * All parallel batches launch concurrently (single Claude message) * Sequential batches execute in order * No concurrency limit for CLI tools * Execution flow: parallel-first → sequential-after - Enhance code review with task.json acceptance criteria * Unified review template for all tools (Gemini/Qwen/Codex/Agent) * task.json in CONTEXT field for CLI tools * Explicit acceptance criteria verification - Clarify execution requirements * Remove ambiguous strategy instructions * Add explicit "MUST complete ALL tasks" requirement * Prevent CLI tools from partial execution misunderstanding - Simplify documentation (~260 lines reduced) * Remove redundant examples and explanations * Keep core algorithm logic with inline comments * Eliminate duplicate descriptions across sections Breaking changes: None (backward compatible)
This commit is contained in:
@@ -185,276 +185,107 @@ Execution Complete
|
||||
previousExecutionResults = []
|
||||
```
|
||||
|
||||
### Step 2: Intelligent Task Grouping & Execution Planning
|
||||
### Step 2: Task Grouping & Batch Creation
|
||||
|
||||
**Operations**:
|
||||
- Analyze task dependencies and file targets
|
||||
- Group tasks based on context similarity and dependencies
|
||||
- Support parallel execution for independent tasks
|
||||
- Respect task limits per execution (Agent: 7 tasks, CLI: 4 tasks)
|
||||
|
||||
**Task Analysis & Grouping**:
|
||||
**Dependency Analysis & Grouping Algorithm**:
|
||||
```javascript
|
||||
// Extract file path from task for dependency analysis
|
||||
function getTaskFile(task) {
|
||||
return task.file || task.title.match(/in\s+([^\s:]+)/)?.[1] || null
|
||||
}
|
||||
|
||||
// Infer dependencies from task descriptions and file paths
|
||||
// Infer dependencies: same file → sequential, keywords (use/integrate) → sequential
|
||||
function inferDependencies(tasks) {
|
||||
return tasks.map((task, index) => {
|
||||
const dependencies = []
|
||||
const taskFile = getTaskFile(task)
|
||||
const keywords = task.description?.toLowerCase() || task.title.toLowerCase()
|
||||
return tasks.map((task, i) => {
|
||||
const deps = []
|
||||
const file = task.file || task.title.match(/in\s+([^\s:]+)/)?.[1]
|
||||
const keywords = (task.description || task.title).toLowerCase()
|
||||
|
||||
// Check previous tasks for dependencies
|
||||
for (let i = 0; i < index; i++) {
|
||||
const prevTask = tasks[i]
|
||||
const prevFile = getTaskFile(prevTask)
|
||||
|
||||
// Same file modification → sequential dependency
|
||||
if (taskFile && prevFile === taskFile) {
|
||||
dependencies.push(i)
|
||||
continue
|
||||
}
|
||||
|
||||
// Keyword-based dependency detection
|
||||
if (keywords.includes('use') || keywords.includes('integrate') ||
|
||||
keywords.includes('call') || keywords.includes('import')) {
|
||||
const prevTitle = prevTask.title.toLowerCase()
|
||||
if (keywords.includes(prevTitle.split(' ')[0])) {
|
||||
dependencies.push(i)
|
||||
}
|
||||
}
|
||||
for (let j = 0; j < i; j++) {
|
||||
const prevFile = tasks[j].file || tasks[j].title.match(/in\s+([^\s:]+)/)?.[1]
|
||||
if (file && prevFile === file) deps.push(j) // Same file
|
||||
else if (/use|integrate|call|import/.test(keywords)) deps.push(j) // Keyword dependency
|
||||
}
|
||||
|
||||
return { ...task, taskIndex: index, dependencies }
|
||||
return { ...task, taskIndex: i, dependencies: deps }
|
||||
})
|
||||
}
|
||||
|
||||
// Group tasks into execution batches
|
||||
// Group into batches: independent → parallel [P1,P2...], dependent → sequential [S1,S2...]
|
||||
function createExecutionCalls(tasks, executionMethod) {
|
||||
const tasksWithDeps = inferDependencies(tasks)
|
||||
const maxTasksPerCall = executionMethod === "Codex" ? 4 : 7
|
||||
const maxBatch = executionMethod === "Codex" ? 4 : 7
|
||||
const calls = []
|
||||
const processed = new Set()
|
||||
|
||||
// Phase 1: Group independent tasks for parallel execution
|
||||
// Parallel: independent tasks, different files, max batch size
|
||||
const parallelGroups = []
|
||||
tasksWithDeps.forEach(task => {
|
||||
if (task.dependencies.length === 0 && !processed.has(task.taskIndex)) {
|
||||
const group = [task]
|
||||
processed.add(task.taskIndex)
|
||||
|
||||
// Find other independent tasks with different files for parallel batch
|
||||
const taskFile = getTaskFile(task)
|
||||
tasksWithDeps.forEach(other => {
|
||||
if (other.dependencies.length === 0 &&
|
||||
!processed.has(other.taskIndex) &&
|
||||
group.length < maxTasksPerCall) {
|
||||
const otherFile = getTaskFile(other)
|
||||
// Only group if different files (avoid conflicts)
|
||||
if (!taskFile || !otherFile || taskFile !== otherFile) {
|
||||
group.push(other)
|
||||
processed.add(other.taskIndex)
|
||||
}
|
||||
tasksWithDeps.forEach(t => {
|
||||
if (t.dependencies.length === 0 && !processed.has(t.taskIndex)) {
|
||||
const group = [t]
|
||||
processed.add(t.taskIndex)
|
||||
tasksWithDeps.forEach(o => {
|
||||
if (!o.dependencies.length && !processed.has(o.taskIndex) &&
|
||||
group.length < maxBatch && t.file !== o.file) {
|
||||
group.push(o)
|
||||
processed.add(o.taskIndex)
|
||||
}
|
||||
})
|
||||
|
||||
parallelGroups.push(group)
|
||||
}
|
||||
})
|
||||
|
||||
// Phase 2: Group dependent tasks sequentially
|
||||
const dependentTasks = tasksWithDeps.filter(t => !processed.has(t.taskIndex))
|
||||
const sequentialBatches = []
|
||||
while (dependentTasks.length > 0) {
|
||||
const batch = []
|
||||
const batchIndices = new Set()
|
||||
|
||||
for (const task of dependentTasks) {
|
||||
// Can add to batch if dependencies are already processed
|
||||
const depsProcessed = task.dependencies.every(dep =>
|
||||
processed.has(dep) || batchIndices.has(dep)
|
||||
)
|
||||
|
||||
if (depsProcessed && batch.length < maxTasksPerCall) {
|
||||
batch.push(task)
|
||||
batchIndices.add(task.taskIndex)
|
||||
processed.add(task.taskIndex)
|
||||
}
|
||||
}
|
||||
|
||||
if (batch.length === 0) break // Prevent infinite loop
|
||||
sequentialBatches.push(batch)
|
||||
dependentTasks.splice(0, dependentTasks.length,
|
||||
...dependentTasks.filter(t => !processed.has(t.taskIndex))
|
||||
// Sequential: dependent tasks, batch when deps satisfied
|
||||
const remaining = tasksWithDeps.filter(t => !processed.has(t.taskIndex))
|
||||
while (remaining.length > 0) {
|
||||
const batch = remaining.filter((t, i) =>
|
||||
i < maxBatch && t.dependencies.every(d => processed.has(d))
|
||||
)
|
||||
if (!batch.length) break
|
||||
batch.forEach(t => processed.add(t.taskIndex))
|
||||
calls.push({ executionType: "sequential", groupId: `S${calls.length + 1}`, tasks: batch })
|
||||
remaining.splice(0, remaining.length, ...remaining.filter(t => !processed.has(t.taskIndex)))
|
||||
}
|
||||
|
||||
// Combine parallel and sequential batches
|
||||
parallelGroups.forEach((group, i) => {
|
||||
calls.push({
|
||||
method: executionMethod === "Codex" ? "Codex" : "Agent",
|
||||
executionType: "parallel",
|
||||
groupId: `P${i + 1}`,
|
||||
taskSummary: group.map(t => t.title).join(' | '),
|
||||
tasks: group
|
||||
})
|
||||
})
|
||||
|
||||
sequentialBatches.forEach((batch, i) => {
|
||||
calls.push({
|
||||
method: executionMethod === "Codex" ? "Codex" : "Agent",
|
||||
executionType: "sequential",
|
||||
groupId: `S${i + 1}`,
|
||||
taskSummary: batch.map(t => t.title).join(' → '),
|
||||
tasks: batch
|
||||
})
|
||||
})
|
||||
|
||||
return calls
|
||||
// Combine results
|
||||
return [
|
||||
...parallelGroups.map((g, i) => ({
|
||||
method: executionMethod, executionType: "parallel", groupId: `P${i+1}`,
|
||||
taskSummary: g.map(t => t.title).join(' | '), tasks: g
|
||||
})),
|
||||
...calls.map(c => ({ ...c, method: executionMethod, taskSummary: c.tasks.map(t => t.title).join(' → ') }))
|
||||
]
|
||||
}
|
||||
|
||||
// Create execution calls with IDs and execution method
|
||||
executionCalls = createExecutionCalls(planObject.tasks, executionMethod).map((call, index) => ({
|
||||
...call,
|
||||
id: `[${call.groupId}]`
|
||||
}))
|
||||
executionCalls = createExecutionCalls(planObject.tasks, executionMethod).map(c => ({ ...c, id: `[${c.groupId}]` }))
|
||||
|
||||
// Create TodoWrite list with execution type indicators
|
||||
TodoWrite({
|
||||
todos: executionCalls.map(call => {
|
||||
const typeIcon = call.executionType === "parallel" ? "⚡" : "→"
|
||||
const typeLabel = call.executionType === "parallel" ? "Parallel" : "Sequential"
|
||||
return {
|
||||
content: `${typeIcon} ${call.id} [${typeLabel}] (${call.tasks.length} tasks: ${call.taskSummary})`,
|
||||
status: "pending",
|
||||
activeForm: `Executing ${call.id} - ${typeLabel} batch (${call.tasks.length} tasks)`
|
||||
}
|
||||
})
|
||||
todos: executionCalls.map(c => ({
|
||||
content: `${c.executionType === "parallel" ? "⚡" : "→"} ${c.id} (${c.tasks.length} tasks)`,
|
||||
status: "pending",
|
||||
activeForm: `Executing ${c.id}`
|
||||
}))
|
||||
})
|
||||
```
|
||||
|
||||
**Grouping Strategy Examples**:
|
||||
|
||||
```javascript
|
||||
// Example 1: Independent tasks, different files → Parallel groups
|
||||
Tasks: [
|
||||
{ title: "Create auth.ts", file: "src/auth.ts" },
|
||||
{ title: "Create utils.ts", file: "src/utils.ts" },
|
||||
{ title: "Create types.ts", file: "src/types.ts" }
|
||||
]
|
||||
Result: [P1] ⚡ Parallel (3 tasks)
|
||||
|
||||
// Example 2: Same file modifications → Sequential batch
|
||||
Tasks: [
|
||||
{ title: "Add function in auth.ts", file: "src/auth.ts" },
|
||||
{ title: "Update function in auth.ts", file: "src/auth.ts" }
|
||||
]
|
||||
Result: [S1] → Sequential (2 tasks)
|
||||
|
||||
// Example 3: Mixed dependencies → Multiple batches
|
||||
Tasks: [
|
||||
{ title: "Create base.ts", file: "src/base.ts" }, // No deps
|
||||
{ title: "Create helper.ts", file: "src/helper.ts" }, // No deps
|
||||
{ title: "Use base in main.ts", file: "src/main.ts" }, // Depends on base.ts
|
||||
{ title: "Use helper in app.ts", file: "src/app.ts" } // Depends on helper.ts
|
||||
]
|
||||
Result:
|
||||
[P1] ⚡ Parallel (2 tasks: base.ts | helper.ts)
|
||||
[P2] ⚡ Parallel (2 tasks: main.ts | app.ts)
|
||||
|
||||
// Example 4: Exceeds batch limit → Multiple batches
|
||||
Agent (7 tasks max): 10 tasks → [P1: 7 tasks] + [P2: 3 tasks]
|
||||
Codex (4 tasks max): 10 tasks → [P1: 4 tasks] + [P2: 4 tasks] + [P3: 2 tasks]
|
||||
```
|
||||
|
||||
**TodoWrite Display Examples**:
|
||||
```
|
||||
Parallel execution (independent files):
|
||||
[ ] ⚡ [P1] [Parallel] (3 tasks: Create auth.ts | Create utils.ts | Create types.ts)
|
||||
|
||||
Sequential execution (same file or dependencies):
|
||||
[ ] → [S1] [Sequential] (2 tasks: Add function in auth.ts → Update function in auth.ts)
|
||||
|
||||
Mixed execution (multiple batches):
|
||||
[ ] ⚡ [P1] [Parallel] (4 tasks: Create base.ts | Create helper.ts | Create config.ts | Create types.ts)
|
||||
[ ] ⚡ [P2] [Parallel] (3 tasks: Use base in main.ts | Use helper in app.ts | Use config in index.ts)
|
||||
[ ] → [S1] [Sequential] (2 tasks: Integrate components → Add tests)
|
||||
```
|
||||
|
||||
### Step 3: Launch Execution
|
||||
|
||||
**Execution Strategy**:
|
||||
- **Parallel batches**: Launch all parallel batches simultaneously (CLI has no concurrency limit)
|
||||
- **Sequential batches**: Execute in order, waiting for previous completion
|
||||
- **Mixed workflow**: Process parallel groups first, then sequential groups
|
||||
|
||||
**Execution Loop**:
|
||||
**Execution Flow**: Parallel batches concurrently → Sequential batches in order
|
||||
```javascript
|
||||
// Phase 1: Launch all parallel batches concurrently
|
||||
const parallelCalls = executionCalls.filter(c => c.executionType === "parallel")
|
||||
const sequentialCalls = executionCalls.filter(c => c.executionType === "sequential")
|
||||
const parallel = executionCalls.filter(c => c.executionType === "parallel")
|
||||
const sequential = executionCalls.filter(c => c.executionType === "sequential")
|
||||
|
||||
if (parallelCalls.length > 0) {
|
||||
// Mark all parallel calls as in_progress
|
||||
TodoWrite({
|
||||
todos: executionCalls.map(call => ({
|
||||
content: `${call.executionType === "parallel" ? "⚡" : "→"} ${call.id} [${call.executionType}] (${call.tasks.length} tasks)`,
|
||||
status: call.executionType === "parallel" ? "in_progress" : "pending",
|
||||
activeForm: `Executing ${call.id}`
|
||||
}))
|
||||
})
|
||||
|
||||
// Launch all parallel batches using single message with multiple tool calls
|
||||
// Use Task tool or Bash tool to launch each parallel execution
|
||||
// Collect results as they complete
|
||||
|
||||
parallelResults = await Promise.all(parallelCalls.map(call => executeBatch(call)))
|
||||
// Phase 1: Launch all parallel batches (single message with multiple tool calls)
|
||||
if (parallel.length > 0) {
|
||||
TodoWrite({ todos: executionCalls.map(c => ({ status: c.executionType === "parallel" ? "in_progress" : "pending" })) })
|
||||
parallelResults = await Promise.all(parallel.map(c => executeBatch(c)))
|
||||
previousExecutionResults.push(...parallelResults)
|
||||
|
||||
// Mark parallel calls as completed
|
||||
TodoWrite({
|
||||
todos: executionCalls.map(call => ({
|
||||
content: `${call.executionType === "parallel" ? "⚡" : "→"} ${call.id} [${call.executionType}] (${call.tasks.length} tasks)`,
|
||||
status: parallelCalls.includes(call) ? "completed" : "pending",
|
||||
activeForm: `Executing ${call.id}`
|
||||
}))
|
||||
})
|
||||
TodoWrite({ todos: executionCalls.map(c => ({ status: parallel.includes(c) ? "completed" : "pending" })) })
|
||||
}
|
||||
|
||||
// Phase 2: Execute sequential batches in order
|
||||
for (const call of sequentialCalls) {
|
||||
// Update TodoWrite: mark current sequential call in_progress
|
||||
TodoWrite({
|
||||
todos: executionCalls.map(c => ({
|
||||
content: `${c.executionType === "parallel" ? "⚡" : "→"} ${c.id} [${c.executionType}] (${c.tasks.length} tasks)`,
|
||||
status: c === call ? "in_progress" : (parallelCalls.includes(c) ? "completed" : "pending"),
|
||||
activeForm: `Executing ${c.id}`
|
||||
}))
|
||||
})
|
||||
|
||||
// Launch sequential execution with previousExecutionResults context
|
||||
// Phase 2: Execute sequential batches one by one
|
||||
for (const call of sequential) {
|
||||
TodoWrite({ todos: executionCalls.map(c => ({ status: c === call ? "in_progress" : "..." })) })
|
||||
result = await executeBatch(call)
|
||||
previousExecutionResults.push(result)
|
||||
|
||||
// Update TodoWrite: mark completed
|
||||
TodoWrite({
|
||||
todos: executionCalls.map(c => ({
|
||||
content: `${c.executionType === "parallel" ? "⚡" : "→"} ${c.id} [${c.executionType}] (${c.tasks.length} tasks)`,
|
||||
status: c.id <= call.id ? "completed" : "pending",
|
||||
activeForm: `Executing ${c.id}`
|
||||
}))
|
||||
})
|
||||
TodoWrite({ todos: executionCalls.map(c => ({ status: "completed" or "pending" })) })
|
||||
}
|
||||
```
|
||||
|
||||
**Important Notes**:
|
||||
- Parallel batches use single Claude message with multiple Bash/Task tool calls
|
||||
- CLI tools (Codex/Gemini/Qwen) execute in foreground (NOT background)
|
||||
- Each batch receives `previousExecutionResults` for context continuity
|
||||
|
||||
**Option A: Agent Execution**
|
||||
|
||||
When to use:
|
||||
@@ -617,35 +448,9 @@ bash_result = Bash(
|
||||
|
||||
**Result Collection**: After completion, analyze output and collect result following `executionResult` structure
|
||||
|
||||
### Step 4: Track Execution Progress
|
||||
### Step 4: Progress Tracking
|
||||
|
||||
**Real-time TodoWrite Updates** at batch level:
|
||||
|
||||
```javascript
|
||||
// Parallel batches executing concurrently
|
||||
TodoWrite({
|
||||
todos: [
|
||||
{ content: "⚡ [P1] [Parallel] (4 tasks)", status: "in_progress", activeForm: "Executing P1" },
|
||||
{ content: "⚡ [P2] [Parallel] (3 tasks)", status: "in_progress", activeForm: "Executing P2" },
|
||||
{ content: "→ [S1] [Sequential] (2 tasks)", status: "pending", activeForm: "..." }
|
||||
]
|
||||
})
|
||||
|
||||
// After parallel completion, sequential execution
|
||||
TodoWrite({
|
||||
todos: [
|
||||
{ content: "⚡ [P1] [Parallel] (4 tasks)", status: "completed", activeForm: "..." },
|
||||
{ content: "⚡ [P2] [Parallel] (3 tasks)", status: "completed", activeForm: "..." },
|
||||
{ content: "→ [S1] [Sequential] (2 tasks)", status: "in_progress", activeForm: "Executing S1" }
|
||||
]
|
||||
})
|
||||
```
|
||||
|
||||
**User Visibility**:
|
||||
- Parallel batches show simultaneous execution (multiple "in_progress" at once)
|
||||
- Sequential batches execute one at a time
|
||||
- Icons distinguish parallel (⚡) from sequential (→)
|
||||
- Task count shows batch size for each execution group
|
||||
Progress tracked at batch level (not individual task level). Icons: ⚡ (parallel, concurrent), → (sequential, one-by-one)
|
||||
|
||||
### Step 5: Code Review (Optional)
|
||||
|
||||
@@ -706,50 +511,9 @@ codex --full-auto exec "[Verify task.json acceptance criteria at ${task.json}]"
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Execution Intelligence
|
||||
|
||||
1. **Intelligent Task Grouping**: Dependency-aware task allocation
|
||||
- Same file modifications → Sequential batch
|
||||
- Different files + no dependencies → Parallel batch
|
||||
- Respects batch limits: Agent (7 tasks), CLI (4 tasks)
|
||||
- Infers dependencies from file paths and keywords
|
||||
|
||||
2. **Parallel Execution**: Maximize throughput for independent tasks
|
||||
- All parallel batches launch simultaneously
|
||||
- No concurrency limit for CLI tools
|
||||
- Reduces total execution time for independent work
|
||||
- Uses single Claude message with multiple tool calls
|
||||
|
||||
3. **Context Continuity**: Each batch receives previous results
|
||||
- Prevents duplication across batches
|
||||
- Maintains coherent implementation flow
|
||||
- Builds on completed work from parallel/sequential batches
|
||||
|
||||
4. **Flexible Execution**: Multiple input modes supported
|
||||
- In-memory: Seamless lite-plan integration
|
||||
- Prompt: Quick standalone execution
|
||||
- File: Intelligent format detection
|
||||
- Enhanced Task JSON (lite-plan export): Full plan extraction
|
||||
- Plain text: Uses as prompt
|
||||
|
||||
### Task Management
|
||||
|
||||
1. **Smart Batch Creation**: Automatic dependency analysis
|
||||
- File path extraction and comparison
|
||||
- Keyword-based dependency inference
|
||||
- Context signature grouping (same files = sequential)
|
||||
- Batch size optimization per execution method
|
||||
|
||||
2. **Live Progress Tracking**: Real-time batch-level updates
|
||||
- Visual distinction: ⚡ (parallel) vs → (sequential)
|
||||
- Shows concurrent execution for parallel batches
|
||||
- Clear batch completion tracking
|
||||
- Task count per batch for progress visibility
|
||||
|
||||
3. **Execution Strategies**:
|
||||
- Parallel-first: All independent batches execute concurrently
|
||||
- Sequential-after: Dependent batches execute in order
|
||||
- Context passing: Each batch receives all previous results
|
||||
**Input Modes**: In-memory (lite-plan), prompt (standalone), file (JSON/text)
|
||||
**Batch Limits**: Agent 7 tasks, CLI 4 tasks
|
||||
**Execution**: Parallel batches use single Claude message with multiple tool calls (no concurrency limit)
|
||||
|
||||
## Error Handling
|
||||
|
||||
|
||||
Reference in New Issue
Block a user