返回 DeepSeek-Reasonix
completion_report.go
根目录 / internal / evidence / completion_report.go
1 package evidence
2
3 import (
4 "encoding/json"
5 "fmt"
6 "slices"
7 "strings"
8 )
9
10 // CompletionStatus is the model's assessment, distinct from execution facts.
11 type CompletionStatus string
12
13 const (
14 CompletionComplete CompletionStatus = "complete"
15 CompletionPartial CompletionStatus = "partial"
16 CompletionBlocked CompletionStatus = "blocked"
17 CompletionFailed CompletionStatus = "failed"
18 )
19
20 // CriterionStatus is the child's claim about one acceptance criterion.
21 type CriterionStatus string
22
23 const (
24 CriterionSatisfied CriterionStatus = "satisfied"
25 CriterionUnsatisfied CriterionStatus = "unsatisfied"
26 )
27
28 // Completion evidence kinds describe the model's supporting information.
29 const (
30 CompletionEvidenceVerification = "verification"
31 CompletionEvidenceReview = "review"
32 CompletionEvidenceDiff = "diff"
33 CompletionEvidenceFiles = "files"
34 CompletionEvidenceManual = "manual"
35 )
36
37 // CompletionEvidence is one proof attached to an acceptance criterion.
38 type CompletionEvidence struct {
39 Kind string `json:"kind"`
40 Summary string `json:"summary"`
41 Command string `json:"command,omitempty"`
42 Paths []string `json:"paths,omitempty"`
43 }
44
45 // AcceptanceCriterion is one checkable condition the sub-task had to meet.
46 type AcceptanceCriterion struct {
47 ID string `json:"id"`
48 Status CriterionStatus `json:"status"`
49 Evidence []CompletionEvidence `json:"evidence,omitempty"`
50 }
51
52 // CompletionReport is the structured payload submitted via complete_subtask.
53 type CompletionReport struct {
54 Status CompletionStatus `json:"status"`
55 Summary string `json:"summary"`
56 Criteria []AcceptanceCriterion `json:"acceptance_criteria,omitempty"`
57 Unresolved []string `json:"unresolved,omitempty"`
58 }
59
60 // ParseCompletionReport validates and normalizes a complete_subtask argument
61 // object. It checks shape only: whether the claims are true is the host's job.
62 func ParseCompletionReport(raw json.RawMessage) (CompletionReport, error) {
63 var r CompletionReport
64 if err := json.Unmarshal(raw, &r); err != nil {
65 return CompletionReport{}, fmt.Errorf("invalid complete_subtask JSON: %w", err)
66 }
67 r.Status = CompletionStatus(strings.ToLower(strings.TrimSpace(string(r.Status))))
68 switch r.Status {
69 case CompletionComplete, CompletionPartial, CompletionBlocked, CompletionFailed:
70 default:
71 return CompletionReport{}, fmt.Errorf("complete_subtask.status must be complete, partial, blocked, or failed")
72 }
73 r.Summary = strings.TrimSpace(r.Summary)
74 if r.Summary == "" {
75 return CompletionReport{}, fmt.Errorf("complete_subtask.summary is required")
76 }
77 for i := range r.Criteria {
78 c := &r.Criteria[i]
79 c.ID = strings.TrimSpace(c.ID)
80 if c.ID == "" {
81 return CompletionReport{}, fmt.Errorf("acceptance_criteria[%d].id is required", i)
82 }
83 c.Status = CriterionStatus(strings.ToLower(strings.TrimSpace(string(c.Status))))
84 switch c.Status {
85 case CriterionSatisfied, CriterionUnsatisfied:
86 default:
87 return CompletionReport{}, fmt.Errorf("acceptance_criteria[%d].status must be satisfied or unsatisfied", i)
88 }
89 for j := range c.Evidence {
90 e := &c.Evidence[j]
91 e.Kind = strings.ToLower(strings.TrimSpace(e.Kind))
92 switch e.Kind {
93 case CompletionEvidenceVerification:
94 if strings.TrimSpace(e.Command) == "" {
95 return CompletionReport{}, fmt.Errorf("acceptance_criteria[%d].evidence[%d]: verification requires command", i, j)
96 }
97 case CompletionEvidenceDiff, CompletionEvidenceFiles:
98 if len(e.Paths) == 0 {
99 return CompletionReport{}, fmt.Errorf("acceptance_criteria[%d].evidence[%d]: %s requires paths", i, j, e.Kind)
100 }
101 case CompletionEvidenceReview, CompletionEvidenceManual:
102 default:
103 return CompletionReport{}, fmt.Errorf("acceptance_criteria[%d].evidence[%d].kind is not a known evidence kind", i, j)
104 }
105 }
106 }
107 return r, nil
108 }
109
110 // LatestCompletionReport returns the most recent successful complete_subtask
111 // payload recorded this run.
112 func (l *Ledger) LatestCompletionReport() (CompletionReport, bool) {
113 if l == nil {
114 return CompletionReport{}, false
115 }
116 l.mu.Lock()
117 defer l.mu.Unlock()
118 for _, r := range slices.Backward(l.receipts) {
119 if r.ToolName != "complete_subtask" || !r.Success {
120 continue
121 }
122 report, err := ParseCompletionReport(r.Args)
123 if err != nil {
124 continue
125 }
126 return report, true
127 }
128 return CompletionReport{}, false
129 }
130
130 lines GO