diff --git a/.claude/commands/workflow/lite-execute.md b/.claude/commands/workflow/lite-execute.md index dbf05002..63622ac3 100644 --- a/.claude/commands/workflow/lite-execute.md +++ b/.claude/commands/workflow/lite-execute.md @@ -149,14 +149,14 @@ Input Parsing: Execution: ├─ Step 1: Initialize result tracking (previousExecutionResults = []) - ├─ Step 2: Task grouping & batch creation (cost-aware) - │ ├─ Build hybrid dependencies (explicit depends_on + file conflicts via modification_points) - │ ├─ Phase 1: Group by execution_group (explicit parallel groups) - │ ├─ Phase 2: Cost-aware batching (MAX_BATCH_COST=8, Low=1/Medium=2/High=4) + ├─ Step 2: Task grouping & batch creation + │ ├─ Extract explicit depends_on (no file/keyword inference) + │ ├─ Group: independent tasks → single parallel batch (maximize utilization) + │ ├─ Group: dependent tasks → sequential phases (respect dependencies) │ └─ Create TodoWrite list for batches ├─ Step 3: Launch execution - │ ├─ Parallel batches: ⚡ concurrent via multiple tool calls - │ └─ Sequential batches: → one by one + │ ├─ Phase 1: All independent tasks (⚡ single batch, concurrent) + │ └─ Phase 2+: Dependent tasks by dependency order ├─ Step 4: Track progress (TodoWrite updates per batch) └─ Step 5: Code review (if codeReviewTool ≠ "Skip") @@ -181,96 +181,67 @@ previousExecutionResults = [] **Dependency Analysis & Grouping Algorithm**: ```javascript -const MAX_BATCH_COST = 8 // Workload limit per batch (tunable) - -// Task cost based on complexity (Low=1, Medium=2, High=4) -function calculateTaskCost(task) { - const costs = { Low: 1, Medium: 2, High: 4 } - return costs[task.complexity] || 1 -} - -// Build hybrid dependencies: explicit depends_on + implicit file conflicts -function buildDependencies(tasks) { +// Use explicit depends_on from plan.json (no inference from file/keywords) +function extractDependencies(tasks) { const taskIdToIndex = {} tasks.forEach((t, i) => { taskIdToIndex[t.id] = i }) - const fileOwner = {} // Track which task last modified each file return tasks.map((task, i) => { - const deps = new Set() - - // 1. Explicit depends_on - ;(task.depends_on || []).forEach(depId => { - const idx = taskIdToIndex[depId] - if (idx !== undefined && idx < i) deps.add(idx) - }) - - // 2. Implicit file conflicts via modification_points - ;(task.modification_points || []).forEach(mp => { - if (fileOwner[mp.file] !== undefined && fileOwner[mp.file] !== i) { - deps.add(fileOwner[mp.file]) - } - fileOwner[mp.file] = i - }) - - return { ...task, taskIndex: i, dependencies: [...deps], cost: calculateTaskCost(task) } + // Only use explicit depends_on from plan.json + const deps = (task.depends_on || []) + .map(depId => taskIdToIndex[depId]) + .filter(idx => idx !== undefined && idx < i) + return { ...task, taskIndex: i, dependencies: deps } }) } -// Group into cost-aware batches with execution_group priority +// Group into batches: maximize parallel execution function createExecutionCalls(tasks, executionMethod) { - const tasksWithDeps = buildDependencies(tasks) + const tasksWithDeps = extractDependencies(tasks) const processed = new Set() const calls = [] - let parallelIdx = 1, sequentialIdx = 1 - // Phase 1: Group by execution_group (explicit parallel groups) - const groups = {} - tasksWithDeps.forEach(t => { - if (t.execution_group) { - groups[t.execution_group] = groups[t.execution_group] || [] - groups[t.execution_group].push(t) - } - }) - Object.entries(groups).forEach(([groupId, groupTasks]) => { - groupTasks.forEach(t => processed.add(t.taskIndex)) + // Phase 1: All independent tasks → single parallel batch (maximize utilization) + const independentTasks = tasksWithDeps.filter(t => t.dependencies.length === 0) + if (independentTasks.length > 0) { + independentTasks.forEach(t => processed.add(t.taskIndex)) calls.push({ method: executionMethod, executionType: "parallel", - groupId: `P${parallelIdx++}`, - taskSummary: groupTasks.map(t => t.title).join(' | '), - tasks: groupTasks + groupId: "P1", + taskSummary: independentTasks.map(t => t.title).join(' | '), + tasks: independentTasks }) - }) + } - // Phase 2: Process remaining by dependency order with cost-aware batching + // Phase 2: Dependent tasks → sequential batches (respect dependencies) + let sequentialIndex = 1 let remaining = tasksWithDeps.filter(t => !processed.has(t.taskIndex)) while (remaining.length > 0) { - const ready = remaining.filter(t => t.dependencies.every(d => processed.has(d))) - if (ready.length === 0) { ready.push(...remaining) } // Break circular deps + // Find tasks whose dependencies are all satisfied + const ready = remaining.filter(t => + t.dependencies.every(d => processed.has(d)) + ) - // Cost-aware batch creation - let batch = [], batchCost = 0 - const stillReady = [...ready] - while (stillReady.length > 0) { - const idx = stillReady.findIndex(t => batchCost + t.cost <= MAX_BATCH_COST) - if (idx === -1) break - const task = stillReady.splice(idx, 1)[0] - batch.push(task) - batchCost += task.cost + if (ready.length === 0) { + console.warn('Circular dependency detected, forcing remaining tasks') + ready.push(...remaining) } - if (batch.length === 0) batch = [ready[0]] // At least one task - batch.forEach(t => processed.add(t.taskIndex)) + // Group ready tasks (can run in parallel within this phase) + ready.forEach(t => processed.add(t.taskIndex)) calls.push({ method: executionMethod, - executionType: batch.length > 1 ? "parallel" : "sequential", - groupId: batch.length > 1 ? `P${parallelIdx++}` : `S${sequentialIdx++}`, - taskSummary: batch.map(t => t.title).join(batch.length > 1 ? ' | ' : ' → '), - tasks: batch + executionType: ready.length > 1 ? "parallel" : "sequential", + groupId: ready.length > 1 ? `P${calls.length + 1}` : `S${sequentialIndex++}`, + taskSummary: ready.map(t => t.title).join(ready.length > 1 ? ' | ' : ' → '), + tasks: ready }) + remaining = remaining.filter(t => !processed.has(t.taskIndex)) } + return calls } @@ -287,30 +258,14 @@ TodoWrite({ ### Step 3: Launch Execution -**Execution Flow**: Parallel batches concurrently (multiple agents) → Sequential batches in order - -**Unified executeBatch dispatcher**: -```javascript -// Dispatch batch to appropriate executor (Agent or Codex) -function executeBatch(batch) { - if (batch.method === "Agent") { - return executeBatchAgent(batch) // See Option A below - } else { - return executeBatchCodex(batch) // See Option B below - } -} -``` - -**Execution orchestration**: +**Execution Flow**: Parallel batches concurrently → Sequential batches in order ```javascript const parallel = executionCalls.filter(c => c.executionType === "parallel") const sequential = executionCalls.filter(c => c.executionType === "sequential") -// Phase 1: Launch all parallel batches (MULTIPLE CONCURRENT AGENTS) -// Each batch → separate agent instance → true parallelism +// 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" })) }) - // Promise.all dispatches each batch to its own agent concurrently parallelResults = await Promise.all(parallel.map(c => executeBatch(c))) previousExecutionResults.push(...parallelResults) TodoWrite({ todos: executionCalls.map(c => ({ status: parallel.includes(c) ? "completed" : "pending" })) }) @@ -353,12 +308,10 @@ ${task.acceptance.map((criterion, i) => `${i + 1}. ${criterion}`).join('\n')} ` } -// executeBatch(batch) - called for each parallel/sequential batch -function executeBatchAgent(batch) { - return Task( - subagent_type="code-developer", - description=`Implement batch ${batch.groupId}: ${batch.taskSummary}`, - prompt=` +Task( + subagent_type="code-developer", + description="Implement planned tasks", + prompt=` ${originalUserInput ? `## Original User Request\n${originalUserInput}\n\n` : ''} ## Implementation Plan @@ -366,8 +319,8 @@ function executeBatchAgent(batch) { **Summary**: ${planObject.summary} **Approach**: ${planObject.approach} - ## Batch ${batch.groupId}: Task Breakdown (${batch.tasks.length} tasks) - ${batch.tasks.map((task, i) => formatTaskForAgent(task, i)).join('\n')} + ## Task Breakdown (${planObject.tasks.length} tasks) + ${planObject.tasks.map((task, i) => formatTaskForAgent(task, i)).join('\n')} ${previousExecutionResults.length > 0 ? `\n## Previous Execution Results\n${previousExecutionResults.map(result => ` [${result.executionId}] ${result.status} @@ -408,11 +361,10 @@ ${result.notes ? `Notes: ${result.notes}` : ''} Read exploration files for comprehensive context from multiple angles.` : ''} ## Requirements - MUST complete ALL ${batch.tasks.length} tasks in batch ${batch.groupId}. - Return only after all tasks in this batch are fully implemented and tested. + MUST complete ALL ${planObject.tasks.length} tasks listed above in this single execution. + Return only after all tasks are fully implemented and tested. ` - ) -} +) ``` **Result Collection**: After completion, collect result following `executionResult` structure (see Data Structures section) @@ -444,11 +396,7 @@ ${task.acceptance.map((criterion, i) => ` - ${criterion}`).join('\n')} ` } -// executeBatch(batch) - called for each parallel/sequential batch -const timeoutByComplexity = { "Low": 2400000, "Medium": 3600000, "High": 6000000 } - -function executeBatchCodex(batch) { - return Bash(`codex --full-auto exec " +codex --full-auto exec " ${originalUserInput ? `## Original User Request\n${originalUserInput}\n\n` : ''} ## Implementation Plan @@ -456,8 +404,8 @@ ${originalUserInput ? `## Original User Request\n${originalUserInput}\n\n` : ''} TASK: ${planObject.summary} APPROACH: ${planObject.approach} -### Batch ${batch.groupId}: Task Breakdown (${batch.tasks.length} tasks) -${batch.tasks.map((task, i) => formatTaskForCodex(task, i)).join('\n')} +### Task Breakdown (${planObject.tasks.length} tasks) +${planObject.tasks.map((task, i) => formatTaskForCodex(task, i)).join('\n')} ${previousExecutionResults.length > 0 ? `\n### Previous Execution Results\n${previousExecutionResults.map(result => ` [${result.executionId}] ${result.status} @@ -501,15 +449,30 @@ Read exploration files for comprehensive architectural, pattern, and constraint ` : ''} ## Requirements -MUST complete ALL ${batch.tasks.length} tasks in batch ${batch.groupId}. -Return only after all tasks in this batch are fully implemented and tested. +MUST complete ALL ${planObject.tasks.length} tasks listed above in this single execution. +Return only after all tasks are fully implemented and tested. Complexity: ${planObject.complexity} -" --skip-git-repo-check -s danger-full-access`, { timeout: timeoutByComplexity[planObject.complexity] || 3600000 }) -} +" --skip-git-repo-check -s danger-full-access ``` -**Note**: Timeout based on complexity (Low=40min, Medium=60min, High=100min) is handled in `executeBatchCodex`. +**Execution with tracking**: +```javascript +// Launch CLI in foreground (NOT background) +// Timeout based on complexity: Low=40min, Medium=60min, High=100min +const timeoutByComplexity = { + "Low": 2400000, // 40 minutes + "Medium": 3600000, // 60 minutes + "High": 6000000 // 100 minutes +} + +bash_result = Bash( + command=cli_command, + timeout=timeoutByComplexity[planObject.complexity] || 3600000 +) + +// Update TodoWrite when execution completes +``` **Result Collection**: After completion, analyze output and collect result following `executionResult` structure @@ -576,8 +539,8 @@ codex --full-auto exec "[Verify plan acceptance criteria at ${plan.json}]" --ski ## Best Practices **Input Modes**: In-memory (lite-plan), prompt (standalone), file (JSON/text) -**Task Grouping**: Hybrid dependencies (explicit depends_on + file conflicts) + execution_group priority + cost-aware batching (MAX_BATCH_COST=8) -**Execution**: Parallel batches via single Claude message with multiple tool calls; cost balances workload (Low=1, Medium=2, High=4) +**Task Grouping**: Based on explicit depends_on only; independent tasks run in single parallel batch +**Execution**: All independent tasks launch concurrently via single Claude message with multiple tool calls ## Error Handling diff --git a/.claude/commands/workflow/lite-plan.md b/.claude/commands/workflow/lite-plan.md index 467fcee3..2314321d 100644 --- a/.claude/commands/workflow/lite-plan.md +++ b/.claude/commands/workflow/lite-plan.md @@ -415,8 +415,6 @@ Generate plan.json with: 3. **Substantial tasks**: Each task should represent 15-60 minutes of work 4. **True dependencies only**: Only use depends_on when Task B cannot start without Task A's output 5. **Prefer parallel**: Most tasks should be independent (no depends_on) -6. **Explicit parallel groups**: Assign same `execution_group` ID (e.g., "group-1") to tasks that can run concurrently; null for others -7. **Task complexity**: Set `complexity` (Low/Medium/High) for workload balancing - executor uses cost units (1/2/4) ## Execution 1. Read ALL exploration files for comprehensive context diff --git a/.claude/workflows/cli-templates/schemas/plan-json-schema.json b/.claude/workflows/cli-templates/schemas/plan-json-schema.json index 6a548e18..b94e2d58 100644 --- a/.claude/workflows/cli-templates/schemas/plan-json-schema.json +++ b/.claude/workflows/cli-templates/schemas/plan-json-schema.json @@ -118,15 +118,6 @@ "pattern": "^T[0-9]+$" }, "description": "Task IDs this task depends on (e.g., ['T1', 'T2'])" - }, - "execution_group": { - "type": ["string", "null"], - "description": "Parallel execution group ID. Tasks with same group ID run concurrently. null = use depends_on logic" - }, - "complexity": { - "type": "string", - "enum": ["Low", "Medium", "High"], - "description": "Task complexity for workload balancing (Low=1, Medium=2, High=4 cost units)" } } },