refactor: Update task structure and grouping principles in planning and execution schemas for clarity and consistency

This commit is contained in:
catlog22
2025-11-28 10:56:45 +08:00
parent beb839d8e2
commit 196b805499
3 changed files with 85 additions and 65 deletions

View File

@@ -150,12 +150,13 @@ Input Parsing:
Execution: Execution:
├─ Step 1: Initialize result tracking (previousExecutionResults = []) ├─ Step 1: Initialize result tracking (previousExecutionResults = [])
├─ Step 2: Task grouping & batch creation ├─ Step 2: Task grouping & batch creation
│ ├─ Infer dependencies (same file → sequential, keywords → sequential) │ ├─ Extract explicit depends_on (no file/keyword inference)
│ ├─ Group into batches (parallel: independent, sequential: dependent) │ ├─ Group: independent tasks → single parallel batch (maximize utilization)
│ ├─ Group: dependent tasks → sequential phases (respect dependencies)
│ └─ Create TodoWrite list for batches │ └─ Create TodoWrite list for batches
├─ Step 3: Launch execution ├─ Step 3: Launch execution
│ ├─ Phase 1: All parallel batches (⚡ concurrent via multiple tool calls) │ ├─ Phase 1: All independent tasks (⚡ single batch, concurrent)
│ └─ Phase 2: Sequential batches (→ one by one) │ └─ Phase 2+: Dependent tasks by dependency order
├─ Step 4: Track progress (TodoWrite updates per batch) ├─ Step 4: Track progress (TodoWrite updates per batch)
└─ Step 5: Code review (if codeReviewTool ≠ "Skip") └─ Step 5: Code review (if codeReviewTool ≠ "Skip")
@@ -180,66 +181,68 @@ previousExecutionResults = []
**Dependency Analysis & Grouping Algorithm**: **Dependency Analysis & Grouping Algorithm**:
```javascript ```javascript
// Infer dependencies: same file → sequential, keywords (use/integrate) → sequential // Use explicit depends_on from plan.json (no inference from file/keywords)
function inferDependencies(tasks) { function extractDependencies(tasks) {
return tasks.map((task, i) => { const taskIdToIndex = {}
const deps = [] tasks.forEach((t, i) => { taskIdToIndex[t.id] = i })
const file = task.file || task.title.match(/in\s+([^\s:]+)/)?.[1]
const keywords = (task.description || task.title).toLowerCase()
for (let j = 0; j < i; j++) { return tasks.map((task, i) => {
const prevFile = tasks[j].file || tasks[j].title.match(/in\s+([^\s:]+)/)?.[1] // Only use explicit depends_on from plan.json
if (file && prevFile === file) deps.push(j) // Same file const deps = (task.depends_on || [])
else if (/use|integrate|call|import/.test(keywords)) deps.push(j) // Keyword dependency .map(depId => taskIdToIndex[depId])
} .filter(idx => idx !== undefined && idx < i)
return { ...task, taskIndex: i, dependencies: deps } return { ...task, taskIndex: i, dependencies: deps }
}) })
} }
// Group into batches: independent → parallel [P1,P2...], dependent → sequential [S1,S2...] // Group into batches: maximize parallel execution
function createExecutionCalls(tasks, executionMethod) { function createExecutionCalls(tasks, executionMethod) {
const tasksWithDeps = inferDependencies(tasks) const tasksWithDeps = extractDependencies(tasks)
const maxBatch = executionMethod === "Codex" ? 4 : 7
const calls = []
const processed = new Set() const processed = new Set()
const calls = []
// Parallel: independent tasks, different files, max batch size // Phase 1: All independent tasks → single parallel batch (maximize utilization)
const parallelGroups = [] const independentTasks = tasksWithDeps.filter(t => t.dependencies.length === 0)
tasksWithDeps.forEach(t => { if (independentTasks.length > 0) {
if (t.dependencies.length === 0 && !processed.has(t.taskIndex)) { independentTasks.forEach(t => processed.add(t.taskIndex))
const group = [t] calls.push({
processed.add(t.taskIndex) method: executionMethod,
tasksWithDeps.forEach(o => { executionType: "parallel",
if (!o.dependencies.length && !processed.has(o.taskIndex) && groupId: "P1",
group.length < maxBatch && t.file !== o.file) { taskSummary: independentTasks.map(t => t.title).join(' | '),
group.push(o) tasks: independentTasks
processed.add(o.taskIndex) })
}
})
parallelGroups.push(group)
}
})
// 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 results // Phase 2: Dependent tasks → sequential batches (respect dependencies)
return [ let sequentialIndex = 1
...parallelGroups.map((g, i) => ({ let remaining = tasksWithDeps.filter(t => !processed.has(t.taskIndex))
method: executionMethod, executionType: "parallel", groupId: `P${i+1}`,
taskSummary: g.map(t => t.title).join(' | '), tasks: g while (remaining.length > 0) {
})), // Find tasks whose dependencies are all satisfied
...calls.map(c => ({ ...c, method: executionMethod, taskSummary: c.tasks.map(t => t.title).join(' → ') })) const ready = remaining.filter(t =>
] t.dependencies.every(d => processed.has(d))
)
if (ready.length === 0) {
console.warn('Circular dependency detected, forcing remaining tasks')
ready.push(...remaining)
}
// Group ready tasks (can run in parallel within this phase)
ready.forEach(t => processed.add(t.taskIndex))
calls.push({
method: executionMethod,
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
} }
executionCalls = createExecutionCalls(planObject.tasks, executionMethod).map(c => ({ ...c, id: `[${c.groupId}]` })) executionCalls = createExecutionCalls(planObject.tasks, executionMethod).map(c => ({ ...c, id: `[${c.groupId}]` }))
@@ -536,8 +539,8 @@ codex --full-auto exec "[Verify plan acceptance criteria at ${plan.json}]" --ski
## Best Practices ## Best Practices
**Input Modes**: In-memory (lite-plan), prompt (standalone), file (JSON/text) **Input Modes**: In-memory (lite-plan), prompt (standalone), file (JSON/text)
**Batch Limits**: Agent 7 tasks, CLI 4 tasks **Task Grouping**: Based on explicit depends_on only; independent tasks run in single parallel batch
**Execution**: Parallel batches use single Claude message with multiple tool calls (no concurrency limit) **Execution**: All independent tasks launch concurrently via single Claude message with multiple tool calls
## Error Handling ## Error Handling

View File

@@ -373,16 +373,28 @@ ${complexity}
Generate plan.json with: Generate plan.json with:
- summary: 2-3 sentence overview - summary: 2-3 sentence overview
- approach: High-level implementation strategy (incorporating insights from all exploration angles) - approach: High-level implementation strategy (incorporating insights from all exploration angles)
- tasks: 3-10 structured tasks with: - tasks: 3-7 structured tasks (**IMPORTANT: group by feature/module, NOT by file**)
- title, file, action, description - **Task Granularity Principle**: Each task = one complete feature unit or module
- implementation (3-7 steps) - title: action verb + target module/feature (e.g., "Implement auth token refresh")
- scope: module path (src/auth/) or feature name, prefer module-level over single file
- action, description
- modification_points: ALL files to modify for this feature (group related changes)
- implementation (3-7 steps covering all modification_points)
- reference (pattern, files, examples) - reference (pattern, files, examples)
- acceptance (2-4 criteria) - acceptance (2-4 criteria for the entire feature)
- depends_on: task IDs this task depends on (use sparingly, only for true dependencies)
- estimated_time, recommended_execution, complexity - estimated_time, recommended_execution, complexity
- _metadata: - _metadata:
- timestamp, source, planning_mode - timestamp, source, planning_mode
- exploration_angles: ${JSON.stringify(manifest.explorations.map(e => e.angle))} - exploration_angles: ${JSON.stringify(manifest.explorations.map(e => e.angle))}
## Task Grouping Rules
1. **Group by feature**: All changes for one feature = one task (even if 3-5 files)
2. **Avoid file-per-task**: Do NOT create separate tasks for each file
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)
## Execution ## Execution
1. Read ALL exploration files for comprehensive context 1. Read ALL exploration files for comprehensive context
2. Execute CLI planning using Gemini (Qwen fallback) 2. Execute CLI planning using Gemini (Qwen fallback)

View File

@@ -27,7 +27,7 @@
"maxItems": 10, "maxItems": 10,
"items": { "items": {
"type": "object", "type": "object",
"required": ["id", "title", "file", "action", "description", "implementation", "acceptance"], "required": ["id", "title", "scope", "action", "description", "implementation", "acceptance"],
"properties": { "properties": {
"id": { "id": {
"type": "string", "type": "string",
@@ -36,11 +36,15 @@
}, },
"title": { "title": {
"type": "string", "type": "string",
"description": "Task title (action verb + target)" "description": "Task title (action verb + target module/feature)"
},
"scope": {
"type": "string",
"description": "Task scope: module path (src/auth/), feature name, or single file. Prefer module/feature level over single file."
}, },
"file": { "file": {
"type": "string", "type": "string",
"description": "Target file path for this task" "description": "Primary file (deprecated, use scope + modification_points instead)"
}, },
"action": { "action": {
"type": "string", "type": "string",
@@ -53,13 +57,14 @@
}, },
"modification_points": { "modification_points": {
"type": "array", "type": "array",
"minItems": 1,
"items": { "items": {
"type": "object", "type": "object",
"required": ["file", "target", "change"], "required": ["file", "target", "change"],
"properties": { "properties": {
"file": { "file": {
"type": "string", "type": "string",
"description": "File path" "description": "File path within scope"
}, },
"target": { "target": {
"type": "string", "type": "string",
@@ -71,7 +76,7 @@
} }
} }
}, },
"description": "Precise modification points with file:target:change format" "description": "All modification points for this task. Group related changes (same feature/module) into one task with multiple modification_points."
}, },
"implementation": { "implementation": {
"type": "array", "type": "array",