1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
|
package task
// TaskLookup is the minimal interface CurrentAttempt needs: resolving a
// task by ID and finding tasks that depend on it. Both
// internal/executor.Store and internal/scheduler.StoryStore already satisfy
// this structurally, so CurrentAttempt lives here (in the lowest-level
// package both already import) rather than being duplicated in each.
type TaskLookup interface {
GetTask(id string) (*Task, error)
ListDependents(taskID string) ([]*Task, error)
}
// maxAttemptChainWalk bounds CurrentAttempt's forward walk as a defense
// against a pathological/cyclic DependsOn chain (task creation doesn't
// reject cycles) -- in practice, no caller creates chains anywhere near
// this long; a much smaller, intentional policy limit (e.g.
// internal/scheduler's own maxFixAttempts) is what actually bounds real
// fix-attempt chains in normal operation.
const maxAttemptChainWalk = 100
// CurrentAttempt resolves the task that currently represents the logical
// position anchored at anchorID: anchorID itself, or -- if it was rejected
// and a fix attempt exists -- the fix attempt, walking forward through
// however many rejections have occurred. A fix-attempt task is identified
// structurally: a task with the same ParentTaskID as the task it supersedes,
// whose sole DependsOn entry is that task's own ID -- the exact shape a
// fix-attempt is spawned with, whether at a story's root (ParentTaskID ==
// "") or at an arbitrary subtask position (ParentTaskID set). This is the
// one place "which task is current" is ever decided; callers resolve
// through this before checking a position's own completion/verdict state,
// never operating on a raw anchor ID directly once a fix-attempt might
// exist. See docs/superpowers/specs/2026-07-09-recursive-arbitrated-review-design.md.
func CurrentAttempt(store TaskLookup, anchorID string) (*Task, error) {
current, err := store.GetTask(anchorID)
if err != nil {
return nil, err
}
for i := 0; i < maxAttemptChainWalk; i++ {
dependents, err := store.ListDependents(current.ID)
if err != nil {
return nil, err
}
var next *Task
for _, d := range dependents {
if d.ParentTaskID == current.ParentTaskID && len(d.DependsOn) == 1 && d.DependsOn[0] == current.ID {
next = d
break
}
}
if next == nil {
return current, nil
}
current = next
}
return current, nil
}
|