| 1 | package autoresearch |
| 2 | |
| 3 | import "path/filepath" |
| 4 | |
| 5 | func (s *Store) Summary(taskID string) (*Summary, error) { |
| 6 | task, err := s.LoadTask(taskID) |
| 7 | if err != nil { |
| 8 | return nil, err |
| 9 | } |
| 10 | storeRoot, taskRel, err := s.openTaskRoot(taskID) |
| 11 | if err != nil { |
| 12 | return nil, err |
| 13 | } |
| 14 | defer storeRoot.Close() |
| 15 | var progress Progress |
| 16 | if err := readJSONFile(storeRoot, filepath.Join(taskRel, "state", "progress.json"), &progress); err != nil { |
| 17 | return nil, err |
| 18 | } |
| 19 | findings, err := s.Findings(taskID, 0) |
| 20 | if err != nil { |
| 21 | return nil, err |
| 22 | } |
| 23 | lastHeartbeat, _, err := s.LastHeartbeat(taskID) |
| 24 | if err != nil { |
| 25 | return nil, err |
| 26 | } |
| 27 | accepted := acceptedFindingIDs(findings) |
| 28 | openCriteria := make([]CriterionSummary, 0) |
| 29 | for _, criterion := range task.Spec.SuccessCriteria { |
| 30 | count := countAcceptedEvidence(criterion.EvidenceIDs, accepted) |
| 31 | status := "satisfied" |
| 32 | if criterion.Required && count == 0 { |
| 33 | status = "open" |
| 34 | } |
| 35 | if status == "open" { |
| 36 | openCriteria = append(openCriteria, CriterionSummary{ |
| 37 | ID: criterion.ID, |
| 38 | Description: criterion.Description, |
| 39 | Required: criterion.Required, |
| 40 | EvidenceCount: count, |
| 41 | Status: status, |
| 42 | }) |
| 43 | } |
| 44 | } |
| 45 | summary := &Summary{ |
| 46 | TaskID: task.ID, |
| 47 | Goal: task.Spec.Goal, |
| 48 | Status: progress.Status, |
| 49 | Iteration: progress.Iteration, |
| 50 | CurrentDirection: progress.CurrentDirection, |
| 51 | StaleCount: progress.StaleCount, |
| 52 | PivotCount: progress.PivotCount, |
| 53 | PivotRequired: progress.StaleCount >= 2, |
| 54 | LastHeartbeatAt: lastHeartbeat.CreatedAt, |
| 55 | FindingCount: len(findings), |
| 56 | OpenCriteria: openCriteria, |
| 57 | Blocker: progress.BlockedReason, |
| 58 | TaskPath: task.Root, |
| 59 | NextRequiredAction: nextRequiredAction(progress), |
| 60 | } |
| 61 | return summary, nil |
| 62 | } |
| 63 | |
| 64 | func nextRequiredAction(progress Progress) string { |
| 65 | if progress.Status == StatusBlocked { |
| 66 | return "resolve blocker before continuing" |
| 67 | } |
| 68 | if progress.StaleCount >= 4 { |
| 69 | return "ask for the smallest external input needed" |
| 70 | } |
| 71 | if progress.StaleCount >= 2 { |
| 72 | return "make a structural pivot before continuing" |
| 73 | } |
| 74 | return "continue with the next evidence-producing step" |
| 75 | } |
| 76 |