aristo_cli/nudge/
intents.rs1use aristo_core::index::{IndexEntry, IndexFile, Status, VerifyLevel};
9
10#[derive(Debug, Clone)]
14pub struct AuthoredIntent {
15 pub id: String,
16 pub text: String,
17 pub file: String,
18 pub site: String,
19 pub status: Status,
20 pub verify: VerifyLevel,
21 pub text_hash: String,
22 pub body_hash: String,
23}
24
25#[aristo::intent(
26 "Authored intents are exactly the `IndexEntry::Intent` entries — every \
27 one, including documentation-only `verify = false` intents (still claims \
28 the user authored and may want to review). Assumes are excluded: they \
29 state external invariants, not reviewable claims. This is the same set \
30 the engine's review_backlog metric counts, so `aristo review` and the \
31 nudge can never report a different backlog size for the same index.",
32 verify = "test",
33 id = "authored_intents_are_exactly_the_index_intents"
34)]
35pub fn authored_intents(index: &IndexFile) -> Vec<AuthoredIntent> {
38 index
39 .entries
40 .iter()
41 .filter_map(|(id, entry)| match entry {
42 IndexEntry::Intent(e) => Some(AuthoredIntent {
43 id: id.as_str().to_string(),
44 text: e.text.clone(),
45 file: e.file.clone(),
46 site: e.site.clone(),
47 status: e.status,
48 verify: e.verify,
49 text_hash: e.text_hash.as_str().to_string(),
50 body_hash: e.body_hash.as_str().to_string(),
51 }),
52 IndexEntry::Assume(_) => None,
53 })
54 .collect()
55}
56
57#[cfg(test)]
58mod tests {
59 use super::*;
60 use aristo_core::index::{
61 AnnotationId, AnnotationKind, AssumeEntry, BindingState, CoveredRegion, IntentEntry, Meta,
62 Sha256, VerifyMethod,
63 };
64 use std::collections::BTreeMap;
65
66 fn sha(c: char) -> Sha256 {
67 Sha256::parse(&format!("sha256:{}", c.to_string().repeat(64))).unwrap()
68 }
69
70 fn intent(text: &str) -> IndexEntry {
71 IndexEntry::Intent(IntentEntry {
72 text: text.into(),
73 verify: VerifyLevel::Method(VerifyMethod::Test),
74 status: Status::Tested,
75 text_hash: sha('a'),
76 body_hash: sha('b'),
77 file: "src/lib.rs".into(),
78 site: "fn foo".into(),
79 covered_region: CoveredRegion::Function,
80 binding: BindingState::Local,
81 parent: None,
82 last_critiqued_at_text_hash: None,
83 last_critique_finding_count: None,
84 })
85 }
86
87 fn assume(text: &str) -> IndexEntry {
88 IndexEntry::Assume(AssumeEntry {
89 text: text.into(),
90 status: Status::Unknown,
91 text_hash: sha('a'),
92 body_hash: sha('b'),
93 file: "src/lib.rs".into(),
94 site: "fn foo".into(),
95 covered_region: CoveredRegion::Function,
96 linked: None,
97 parent: None,
98 })
99 }
100
101 fn index_with(entries: Vec<(&str, IndexEntry)>) -> IndexFile {
102 let mut idx = IndexFile {
103 meta: Meta {
104 schema_version: 1,
105 generated_by: None,
106 generated_at: None,
107 source_root: None,
108 },
109 entries: BTreeMap::new(),
110 };
111 for (id, e) in entries {
112 idx.entries.insert(AnnotationId::parse(id).unwrap(), e);
113 }
114 idx
115 }
116
117 #[test]
118 fn enumerates_intents_excludes_assumes() {
119 let idx = index_with(vec![
120 ("i_one", intent("first claim")),
121 ("a_one", assume("an external invariant")),
122 ("i_two", intent("second claim")),
123 ]);
124 let got = authored_intents(&idx);
125 let ids: Vec<&str> = got.iter().map(|i| i.id.as_str()).collect();
126 assert_eq!(ids, vec!["i_one", "i_two"]);
128 }
129
130 #[test]
131 fn carries_text_site_and_hashes() {
132 let idx = index_with(vec![("i_one", intent("first claim"))]);
133 let got = authored_intents(&idx);
134 let one = &got[0];
135 assert_eq!(one.text, "first claim");
136 assert_eq!(one.site, "fn foo");
137 assert_eq!(one.text_hash, sha('a').as_str());
138 assert_eq!(one.body_hash, sha('b').as_str());
139 assert_eq!(one.status, Status::Tested);
140 }
141
142 #[test]
143 fn includes_documentation_only_intents() {
144 let mut e = intent("doc only");
147 if let IndexEntry::Intent(ref mut ie) = e {
148 ie.verify = VerifyLevel::Bool(false);
149 }
150 let idx = index_with(vec![("i_doc", e)]);
151 assert_eq!(authored_intents(&idx).len(), 1);
152 }
153
154 #[test]
157 fn annotation_kind_is_intent_or_assume() {
158 assert_ne!(AnnotationKind::Intent, AnnotationKind::Assume);
159 }
160}