1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum Class {
9 Domain,
11 Progress,
13}
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub struct Name {
18 pub name: &'static str,
20 pub class: Class,
22}
23
24const fn p(name: &'static str) -> Name {
25 Name {
26 name,
27 class: Class::Progress,
28 }
29}
30
31const fn d(name: &'static str) -> Name {
32 Name {
33 name,
34 class: Class::Domain,
35 }
36}
37
38pub const GRANDFATHERED: &[&str] = &[
40 "tool_call",
41 "usage",
42 "said",
43 "reasoning",
44 "notice",
45 "result",
46];
47
48pub const REGISTRY: &[Name] = &[
50 p("session.started"),
51 p("run.started"),
52 p("task.started"),
53 p("stage.started"),
54 p("stage.finished"),
55 p("task.resumed"),
56 p("tool_call"),
57 p("approval.requested"),
58 p("approval.decided"),
59 p("usage"),
60 p("task.finished"),
61 p("agent.started"),
62 p("agent.plan"),
63 p("agent.unasked"),
64 p("said"),
65 p("reasoning"),
66 p("notice"),
67 p("result"),
68 d("bug.recorded"),
69 p("bug.reproduce_try"),
70 p("bug.reproduced"),
71 p("bug.confirm_waiting"),
72 d("bug.confirmed"),
73 d("bug.confirm_rejected"),
74 p("bug.unchanged_selected"),
75 p("bug.discrimination"),
76 d("bug.outcome"),
77 p("oracle.stage"),
78 d("oracle.frozen"),
79 d("oracle.lapsed"),
80 d("collection.checked"),
81 d("project.created"),
82 d("project.undone"),
83 p("scaffold.started"),
84 p("scaffold.finished"),
85 p("planning.stopped"),
86 p("ladder.offered"),
87 d("unit.promoted"),
88 p("ask.step"),
89 p("ask.text"),
90 p("ask.done"),
91 d("task.ticked"),
92 d("task.stopped"),
93];
94
95#[must_use]
97pub fn lookup(name: &str) -> Option<&'static Name> {
98 REGISTRY.iter().find(|n| n.name == name)
99}
100
101#[must_use]
104pub fn well_formed(name: &str) -> bool {
105 let word = |w: &str| !w.is_empty() && w.chars().all(|c| c.is_ascii_lowercase() || c == '_');
106 match name.split_once('.') {
107 Some((a, b)) => word(a) && word(b),
108 None => false,
109 }
110}
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115
116 #[test]
117 fn every_name_is_registered_once_and_new_ones_follow_the_rule() {
118 let mut seen = std::collections::BTreeSet::new();
119 for n in REGISTRY {
120 assert!(seen.insert(n.name), "{} twice", n.name);
121 assert!(
122 well_formed(n.name) || GRANDFATHERED.contains(&n.name),
123 "{} breaks the name rule",
124 n.name
125 );
126 }
127 for g in GRANDFATHERED {
128 assert!(lookup(g).is_some(), "{g}");
129 }
130 assert!(well_formed("bug.reproduce_try"));
131 assert!(!well_formed("bug_outcome"));
132 assert!(!well_formed("Bug.Outcome"));
133 assert!(!well_formed("bug.out.come"));
134 assert_eq!(lookup("bug.outcome").map(|n| n.class), Some(Class::Domain));
135 assert!(lookup("bug.unknown").is_none());
136 }
137}