Post-process plan steps: merge same-file steps and cap count. Recursive planning (Phase 4) self-corrects waste steps and quality, so this only handles structural deduplication. Run: steps are never merged — running the same file twice is intentional (e.g. "run it again to veri
(tasks)
| 133 | |
| 134 | |
| 135 | def _postprocess_plan(tasks): |
| 136 | """ |
| 137 | Post-process plan steps: merge same-file steps and cap count. |
| 138 | |
| 139 | Recursive planning (Phase 4) self-corrects waste steps and quality, |
| 140 | so this only handles structural deduplication. |
| 141 | |
| 142 | Run: steps are never merged — running the same file twice is intentional |
| 143 | (e.g. "run it again to verify"). Only Create/Write/Update steps are |
| 144 | deduplicated by shared filename. |
| 145 | """ |
| 146 | if not tasks: |
| 147 | return tasks |
| 148 | |
| 149 | # Merge steps targeting the same file — keep the longer description. |
| 150 | # Run: steps are passed through unchanged so intentional duplicate runs |
| 151 | # (e.g. "run wordcount.py twice") are preserved. |
| 152 | merged = [] |
| 153 | seen_files = {} # filename -> index in merged list |
| 154 | for t in tasks: |
| 155 | if _RUN_STEP_RE.match(t): |
| 156 | # Shell-command steps: always keep as-is, never deduplicate |
| 157 | merged.append(t) |
| 158 | continue |
| 159 | files_in_step = _FILE_RE.findall(t) |
| 160 | merged_into = None |
| 161 | for f in files_in_step: |
| 162 | if f in seen_files: |
| 163 | merged_into = seen_files[f] |
| 164 | break |
| 165 | if merged_into is not None: |
| 166 | if len(t) > len(merged[merged_into]): |
| 167 | merged[merged_into] = t |
| 168 | else: |
| 169 | idx = len(merged) |
| 170 | merged.append(t) |
| 171 | for f in files_in_step: |
| 172 | seen_files[f] = idx |
| 173 | |
| 174 | return merged[:8] |
| 175 | |
| 176 | def plan_tasks(user_message, project_context=''): |
| 177 | """ |