(s: TaskState, path: Set<string>)
| 169 | if (!planTasks(mgr.plan).some(t => t.name === task.name)) return false; |
| 170 | const memo = new Map<string, boolean>(); |
| 171 | const visit = (s: TaskState, path: Set<string>): boolean => { |
| 172 | const cached = memo.get(s.name); |
| 173 | if (cached !== undefined) return cached; |
| 174 | if (path.has(s.name)) { |
| 175 | memo.set(s.name, false); |
| 176 | return false; |
| 177 | } |
| 178 | |
| 179 | const nextPath = new Set(path); |
| 180 | nextPath.add(s.name); |
| 181 | // Pending chains can hide terminal blockers behind direct pending deps. |
| 182 | for (const dep of s.dependsOn) { |
| 183 | const ds = mgr.getTask(dep); |
| 184 | if (!ds || isFailedTerminalStatus(ds.status)) { |
| 185 | memo.set(s.name, false); |
| 186 | return false; |
| 187 | } |
| 188 | if (ds.status !== "handed-off" && !visit(ds, nextPath)) { |
| 189 | memo.set(s.name, false); |
| 190 | return false; |
| 191 | } |
| 192 | } |
| 193 | memo.set(s.name, true); |
| 194 | return true; |
| 195 | }; |
| 196 | return visit(task, new Set()); |
| 197 | } |
| 198 |
no test coverage detected