speccoder_runtime/bug/
handoff.rs1use speccoder_core::bug::{BugReport, Claim, Handoff, Outcome, Statements};
6use speccoder_core::next::NextStep;
7use speccoder_core::verify::RedClass;
8
9#[must_use]
11pub fn next(bug: &BugReport, outcome: Outcome) -> Vec<NextStep> {
12 let key = bug.key();
13 let step = |command: String, why: &str| NextStep {
14 command,
15 why: why.to_owned(),
16 };
17 let reword = || {
18 if bug.home.kind.native() {
19 step(
20 format!(
21 "speccoder bug --slug {}",
22 bug.task.trim_start_matches("bug:")
23 ),
24 "after rewording the assessment's statements",
25 )
26 } else {
27 step(
28 format!(
29 "speccoder review edit {} --feature {} \"…\"",
30 bug.requirement, bug.unit
31 ),
32 "reword the triad (the confirmation lapses)",
33 )
34 }
35 };
36 match outcome {
37 Outcome::CannotReproduce | Outcome::UnchangedNotHolding => vec![
38 step(
39 format!(
40 "speccoder bug \"{}\" --expected \"…\" --test <file>::<test>",
41 bug.triad.current.text
42 ),
43 "bring a failing test yourself",
44 ),
45 reword(),
46 step(
47 format!("speccoder bug run --task {key}"),
48 "try reproducing again",
49 ),
50 step(
51 format!(
52 "speccoder task skip {} --feature {} --reason \"…\"",
53 bug.task, bug.unit
54 ),
55 "drop it",
56 ),
57 ],
58 Outcome::AlreadySatisfied if bug.commit.is_some() => vec![step(
59 format!("speccoder report --feature {}", bug.unit),
60 "kept as a regression test: the evidence",
61 )],
62 Outcome::AlreadySatisfied => vec![
63 step(
64 format!("speccoder bug keep {key} --reason \"kept as a regression test\""),
65 "keep the reproduction as a regression test: committed, the task attested",
66 ),
67 step(
68 format!(
69 "speccoder task skip {} --feature {} --reason \"already fixed\"",
70 bug.task, bug.unit
71 ),
72 "drop it",
73 ),
74 ],
75 Outcome::AwaitingConfirmation => vec![
76 step(
77 format!(
78 "speccoder bug confirm {key} --digest {}",
79 bug.confirm_digest.clone().unwrap_or_default()
80 ),
81 "the reproductions show the bug: fix it",
82 ),
83 step(
84 format!("speccoder bug confirm {key} --reject --reason \"…\""),
85 "they do not: the author tries again, told why",
86 ),
87 ],
88 Outcome::Recorded => vec![step(
89 format!("speccoder bug run --task {key}"),
90 "reproduce it",
91 )],
92 Outcome::Fixed | Outcome::Failed => vec![step(
93 format!("speccoder report --feature {}", bug.unit),
94 "the evidence",
95 )],
96 }
97}
98
99#[must_use]
102pub fn compose(
103 bug: &BugReport,
104 outcome: Outcome,
105 elapsed_ms: u64,
106 cost: Option<serde_json::Value>,
107 claim: Option<String>,
108) -> Handoff {
109 let mut statements = Statements::default();
110 for e in bug.expected_ids() {
111 let classes: Vec<RedClass> = bug
113 .tries
114 .iter()
115 .filter(|t| t.discarded.is_none())
116 .filter_map(|t| {
117 t.per_statement
118 .iter()
119 .find(|s| s.statement == e)
120 .map(|s| s.class)
121 })
122 .collect();
123 if classes.contains(&RedClass::Assertion) {
124 statements.reproduced.push(e);
125 } else if !classes.is_empty() && classes.iter().all(|c| *c == RedClass::Passed) {
126 statements.satisfied.push(e);
127 } else {
128 statements.not_reproduced.push(e);
129 }
130 }
131 let mut tries = bug.tries.clone();
132 for t in &mut tries {
133 for s in &mut t.per_statement {
134 s.excerpt = s.excerpt.as_ref().map(|x| cut(x, 20));
135 }
136 }
137 Handoff {
138 outcome,
139 tries,
140 statements,
141 cost,
142 elapsed_ms,
143 next: next(bug, outcome),
144 claim: claim.filter(|c| !c.trim().is_empty()).map(|text| Claim {
145 text,
146 by: "author".to_owned(),
147 }),
148 }
149}
150
151#[must_use]
153pub fn cut(text: &str, n: usize) -> String {
154 text.lines()
155 .filter(|l| !l.trim().is_empty())
156 .take(n)
157 .collect::<Vec<_>>()
158 .join("\n")
159}