Skip to main content

splice/hints/
mod.rs

1//! Tool hints derivation for LLM guidance.
2//!
3//! This module provides behavioral flags for refactoring operations,
4//! helping LLMs understand safe operations and potential side effects.
5
6use crate::ingest::SemanticKind;
7use serde::{Deserialize, Serialize};
8
9/// Operation type for tool hint derivation.
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum ToolHintOperation {
12    /// Delete function body only
13    DeleteBody,
14    /// Change function signature (parameters, return type)
15    ChangeSignature,
16    /// Change type definition (struct fields, enum variants)
17    ChangeType,
18    /// Replace entire function body
19    ReplaceBody,
20    /// Query/search for symbols
21    Query,
22    /// Get/retrieve symbol content
23    Get,
24}
25
26/// Behavioral hints for refactoring operations.
27///
28/// Provides LLMs with guidance on safe operations and potential side effects.
29#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
30pub struct ToolHints {
31    /// Whether this operation requires full context (closures, impl blocks).
32    pub requires_full_context: bool,
33
34    /// Whether this operation must be applied atomically (always true for splice).
35    pub apply_atomically: bool,
36
37    /// Whether this operation may break tests (public functions, trait changes).
38    pub may_break_tests: bool,
39
40    /// Whether this operation requires compilation (type-changing operations).
41    pub requires_compilation: bool,
42}
43
44impl Default for ToolHints {
45    fn default() -> Self {
46        Self {
47            requires_full_context: false,
48            apply_atomically: true, // All splice operations are atomic
49            may_break_tests: false,
50            requires_compilation: false,
51        }
52    }
53}
54
55impl ToolHints {
56    /// Create a new ToolHints with default values.
57    pub fn new() -> Self {
58        Self::default()
59    }
60
61    /// Set requires_full_context flag.
62    pub fn with_requires_full_context(mut self, value: bool) -> Self {
63        self.requires_full_context = value;
64        self
65    }
66
67    /// Set may_break_tests flag.
68    pub fn with_may_break_tests(mut self, value: bool) -> Self {
69        self.may_break_tests = value;
70        self
71    }
72
73    /// Set requires_compilation flag.
74    pub fn with_requires_compilation(mut self, value: bool) -> Self {
75        self.requires_compilation = value;
76        self
77    }
78
79    /// Convenience constructor for function deletion.
80    ///
81    /// # Arguments
82    ///
83    /// * `is_public` - Whether the function is publicly visible
84    pub fn for_function_delete(is_public: bool) -> Self {
85        Self {
86            requires_full_context: false,
87            apply_atomically: true,
88            may_break_tests: is_public,
89            requires_compilation: true,
90        }
91    }
92
93    /// Convenience constructor for struct modification.
94    ///
95    /// # Arguments
96    ///
97    /// * `is_public` - Whether the struct is publicly visible
98    pub fn for_struct_modify(is_public: bool) -> Self {
99        Self {
100            requires_full_context: false,
101            apply_atomically: true,
102            may_break_tests: is_public,
103            requires_compilation: true,
104        }
105    }
106
107    /// Convenience constructor for body replacement.
108    ///
109    /// Body replacements are generally safe - they don't change signatures
110    /// or types, so they won't break tests and don't require full context.
111    pub fn for_body_replace() -> Self {
112        Self {
113            requires_full_context: false,
114            apply_atomically: true,
115            may_break_tests: false,
116            requires_compilation: false,
117        }
118    }
119}
120
121/// Derive tool hints from symbol metadata and operation type.
122///
123/// Analyzes the semantic kind, visibility, and operation to determine
124/// appropriate behavioral flags for LLM guidance.
125///
126/// # Arguments
127///
128/// * `semantic_kind` - The kind of symbol (function, type, trait, etc.)
129/// * `is_public` - Whether the symbol is publicly visible
130/// * `operation` - The type of operation being performed
131///
132/// # Returns
133///
134/// ToolHints with appropriate flags set based on static analysis
135pub fn derive_tool_hints(
136    semantic_kind: SemanticKind,
137    is_public: bool,
138    operation: ToolHintOperation,
139) -> ToolHints {
140    // Determine requires_full_context
141    // True for: closures, impl blocks (traits from impl_item)
142    let requires_full_context = matches!(
143        (semantic_kind, operation),
144        (SemanticKind::Function, ToolHintOperation::DeleteBody)
145            | (SemanticKind::Function, ToolHintOperation::ReplaceBody)
146            | (SemanticKind::Trait, _)
147    );
148
149    // apply_atomically is always true (all splice operations are atomic)
150    let apply_atomically = true;
151
152    // Determine may_break_tests using static heuristic
153    // True for: public functions, trait signatures, impl blocks
154    let may_break_tests = match (semantic_kind, is_public) {
155        (SemanticKind::Function, true) => true,
156        (SemanticKind::Trait, true) => true,
157        (SemanticKind::Type, true) => true,
158        _ => false,
159    };
160
161    // Determine requires_compilation
162    // True for: type-changing operations, public signature changes
163    let requires_compilation = match operation {
164        ToolHintOperation::ChangeType => true,
165        ToolHintOperation::ChangeSignature if is_public => true,
166        ToolHintOperation::DeleteBody if is_public => true,
167        _ => false,
168    };
169
170    ToolHints {
171        requires_full_context,
172        apply_atomically,
173        may_break_tests,
174        requires_compilation,
175    }
176}
177
178#[cfg(test)]
179mod tests {
180    use super::*;
181
182    #[test]
183    fn test_tool_hints_default() {
184        let hints = ToolHints::default();
185        assert_eq!(hints.requires_full_context, false);
186        assert_eq!(hints.apply_atomically, true);
187        assert_eq!(hints.may_break_tests, false);
188        assert_eq!(hints.requires_compilation, false);
189    }
190
191    #[test]
192    fn test_tool_hints_builder() {
193        let hints = ToolHints::new()
194            .with_requires_full_context(true)
195            .with_may_break_tests(true)
196            .with_requires_compilation(true);
197
198        assert_eq!(hints.requires_full_context, true);
199        assert_eq!(hints.apply_atomically, true); // Always true
200        assert_eq!(hints.may_break_tests, true);
201        assert_eq!(hints.requires_compilation, true);
202    }
203
204    #[test]
205    fn test_tool_hints_serialization() {
206        let hints = ToolHints::new()
207            .with_requires_full_context(true)
208            .with_may_break_tests(false);
209
210        let json = serde_json::to_string(&hints).unwrap();
211        assert!(json.contains("\"requires_full_context\":true"));
212        assert!(json.contains("\"apply_atomically\":true"));
213        assert!(json.contains("\"may_break_tests\":false"));
214    }
215
216    #[test]
217    fn test_for_function_delete() {
218        let hints = ToolHints::for_function_delete(true);
219        assert_eq!(hints.requires_full_context, false);
220        assert_eq!(hints.apply_atomically, true);
221        assert_eq!(hints.may_break_tests, true); // public function
222        assert_eq!(hints.requires_compilation, true);
223
224        let hints_private = ToolHints::for_function_delete(false);
225        assert_eq!(hints_private.may_break_tests, false); // private function
226    }
227
228    #[test]
229    fn test_for_struct_modify() {
230        let hints = ToolHints::for_struct_modify(true);
231        assert_eq!(hints.requires_full_context, false);
232        assert_eq!(hints.apply_atomically, true);
233        assert_eq!(hints.may_break_tests, true); // public struct
234        assert_eq!(hints.requires_compilation, true);
235
236        let hints_private = ToolHints::for_struct_modify(false);
237        assert_eq!(hints_private.may_break_tests, false); // private struct
238    }
239
240    #[test]
241    fn test_for_body_replace() {
242        let hints = ToolHints::for_body_replace();
243        assert_eq!(hints.requires_full_context, false);
244        assert_eq!(hints.apply_atomically, true);
245        assert_eq!(hints.may_break_tests, false); // body changes don't break tests
246        assert_eq!(hints.requires_compilation, false); // no signature/type change
247    }
248}