Skip to main content

ane/
core.rs

1pub use crate::commands::chord::{ChordResult, FrontendCapabilities, execute_chord, parse_chord};
2pub use crate::commands::chord_engine::ChordEngine;
3pub use crate::commands::chord_engine::errors::ChordError;
4pub use crate::commands::chord_engine::types::{
5    ChordAction, ChordArgs, ChordQuery, ResolvedChord, TextRange,
6};
7pub use crate::commands::diff::unified_diff;
8pub use crate::commands::lsp_engine::{LspEngine, LspEngineConfig};
9pub use crate::data::buffer::Buffer;
10pub use crate::data::chord_types::{Action, Component, Positional, Scope};
11pub use crate::data::skill::SKILL_CONTENT;
12
13use serde::Serialize;
14
15#[derive(Debug, Clone, Serialize)]
16pub struct ToolDefinition {
17    pub name: String,
18    pub description: String,
19    pub input_schema: serde_json::Value,
20}
21
22const TOOL_DESCRIPTION: &str = "\
23Execute a structured chord edit on a file using ane's chord grammar.\n\
24\n\
25Chords are 4 characters: Action + Positional + Scope + Component.\n\
26\n\
27Actions:    c=Change d=Delete r=Replace y=Yank a=Append p=Prepend i=Insert\n\
28Positional: i=Inside e=Entire a=After b=Before n=Next p=Previous u=Until o=Outside t=To\n\
29Scope:      l=Line b=Buffer f=Function v=Variable s=Struct m=Member\n\
30Component:  b=Beginning c=Contents e=End v=Value p=Parameters n=Name s=Self\n\
31\n\
32Args in parens: chord(target:name_or_line_number)\n\
33Use the value parameter (not inline) for replacement text.\n\
34\n\
35Examples:\n\
36  cels(target:3) + value -> change line 3\n\
37  dels(target:5) -> delete line 5\n\
38  cifn(target:getData) + value -> rename function\n\
39  aale(target:10) + value -> append after line 10\n\
40  yefc(target:main) -> yank function body\n\
41  rifc(target:handler) + value -> replace function contents";
42
43pub fn tool_definition() -> ToolDefinition {
44    ToolDefinition {
45        name: "ane".to_string(),
46        description: TOOL_DESCRIPTION.to_string(),
47        input_schema: serde_json::json!({
48            "type": "object",
49            "properties": {
50                "file_path": {
51                    "type": "string",
52                    "description": "Path to the file to edit"
53                },
54                "chord": {
55                    "type": "string",
56                    "description": "Chord expression, e.g. \"cels(target:3)\" or \"cifn(target:getData)\""
57                },
58                "value": {
59                    "type": "string",
60                    "description": "Text for Change/Replace/Append/Insert actions. Preferred over inline value arg for multiline content."
61                }
62            },
63            "required": ["file_path", "chord"]
64        }),
65    }
66}
67
68#[cfg(test)]
69mod tests {
70    use super::*;
71
72    #[test]
73    fn core_parse_round_trip() {
74        let query = parse_chord("cifn").unwrap();
75        assert_eq!(query.action, Action::Change);
76    }
77
78    #[test]
79    fn skill_content_accessible_via_core() {
80        assert!(!SKILL_CONTENT.is_empty());
81    }
82
83    #[test]
84    fn tool_definition_has_correct_name() {
85        assert_eq!(tool_definition().name, "ane");
86    }
87
88    #[test]
89    fn tool_definition_has_non_empty_description() {
90        assert!(!tool_definition().description.is_empty());
91    }
92
93    #[test]
94    fn tool_definition_schema_has_required_fields() {
95        let def = tool_definition();
96        let required = def.input_schema["required"]
97            .as_array()
98            .expect("required should be an array");
99        let fields: Vec<&str> = required.iter().filter_map(|v| v.as_str()).collect();
100        assert!(
101            fields.contains(&"file_path"),
102            "missing file_path in required"
103        );
104        assert!(fields.contains(&"chord"), "missing chord in required");
105    }
106
107    #[test]
108    fn tool_definition_serializes_to_valid_json() {
109        let value = serde_json::to_value(tool_definition()).unwrap();
110        assert!(value.get("name").is_some());
111        assert!(value.get("description").is_some());
112        assert!(value.get("input_schema").is_some());
113    }
114
115    #[test]
116    fn tool_description_under_250_words() {
117        let word_count = TOOL_DESCRIPTION.split_whitespace().count();
118        assert!(
119            word_count <= 250,
120            "TOOL_DESCRIPTION has {word_count} words, expected <= 250"
121        );
122    }
123}