weavatrix_rust_refactor/
contract.rs1use blazingly_json::Value;
9use std::collections::BTreeSet;
10use std::sync::OnceLock;
11
12const FROZEN: &str = include_str!("../contract/refactor-tools.v1.json");
13
14#[derive(Debug, Clone)]
16pub struct ToolContract {
17 pub name: String,
19 pub description: String,
21 pub input_schema: Value,
23}
24
25#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
30pub struct ResultState(pub String);
31
32struct Frozen {
33 tools: Vec<ToolContract>,
34 states: BTreeSet<ResultState>,
35 catalog: Value,
36}
37
38fn frozen() -> &'static Frozen {
39 static FROZEN_ONCE: OnceLock<Frozen> = OnceLock::new();
40 FROZEN_ONCE.get_or_init(|| {
41 let parsed: Value = blazingly_json::from_str(FROZEN)
42 .expect("the embedded contract is written by the build and must parse");
43 let entries = parsed
44 .get("tools")
45 .and_then(Value::as_array)
46 .expect("contract must carry a tools array")
47 .clone();
48 let tools = entries
49 .iter()
50 .map(|tool| ToolContract {
51 name: field(tool, "name"),
52 description: field(tool, "description"),
53 input_schema: tool
54 .get("inputSchema")
55 .cloned()
56 .expect("every contract tool declares an inputSchema"),
57 })
58 .collect::<Vec<_>>();
59 let mut states = BTreeSet::new();
60 if let Some(groups) = parsed.get("resultStates").and_then(Value::as_object) {
61 for (_, group) in groups {
62 for state in group.as_array().into_iter().flatten() {
63 if let Some(text) = state.as_str() {
64 states.insert(ResultState(text.to_owned()));
65 }
66 }
67 }
68 }
69 Frozen {
70 tools,
71 states,
72 catalog: Value::Array(entries),
73 }
74 })
75}
76
77fn field(value: &Value, key: &str) -> String {
78 value
79 .get(key)
80 .and_then(Value::as_str)
81 .unwrap_or_default()
82 .to_owned()
83}
84
85#[must_use]
87pub fn tools() -> &'static [ToolContract] {
88 &frozen().tools
89}
90
91#[must_use]
93pub fn catalog_value() -> &'static Value {
94 &frozen().catalog
95}
96
97#[must_use]
99pub fn declares(name: &str) -> bool {
100 frozen().tools.iter().any(|tool| tool.name == name)
101}
102
103#[must_use]
105pub fn permits_state(state: &str) -> bool {
106 frozen().states.contains(&ResultState(state.to_owned()))
107}
108
109#[cfg(test)]
110mod tests {
111 use super::{catalog_value, declares, permits_state, tools};
112
113 #[test]
114 fn the_contract_freezes_eleven_tools() {
115 assert_eq!(tools().len(), 11);
116 for name in [
117 "rename_symbol",
118 "rename_related_symbols",
119 "apply_edit_plan",
120 "rollback_last_apply",
121 "change_signature",
122 "edit_symbol",
123 "bulk_replace",
124 "organize_imports",
125 "move_file",
126 "move_symbol",
127 "delete_readiness",
128 ] {
129 assert!(declares(name), "{name} is missing from the frozen contract");
130 }
131 }
132
133 #[test]
134 fn every_tool_carries_a_description_and_a_schema() {
135 for tool in tools() {
136 assert!(
137 !tool.description.is_empty(),
138 "{} has no description",
139 tool.name
140 );
141 assert!(
142 tool.input_schema.get("type").is_some(),
143 "{} has no input schema",
144 tool.name
145 );
146 }
147 }
148
149 #[test]
150 fn load_bearing_states_are_permitted_and_invented_ones_are_not() {
151 for state in [
152 "PREVIEW_OK",
153 "APPLIED",
154 "STALE",
155 "REPO_BUSY",
156 "ROLLBACK_INCOMPLETE",
157 "INVALID_ARGS",
158 "PARTIAL",
159 "COMPLETE",
160 "EXACT_LSP",
161 ] {
162 assert!(permits_state(state), "{state} must be a contract state");
163 }
164 assert!(!permits_state("MOSTLY_FINE"));
165 assert!(!permits_state("OK_PROBABLY"));
166 }
167
168 #[test]
169 fn the_catalog_is_the_contract_itself() {
170 let catalog = catalog_value().as_array().expect("catalog is an array");
171 assert_eq!(catalog.len(), tools().len());
172 }
173}