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 = "\
23Use ane for ALL file exploration, discovery, reading, and editing unless the user states otherwise. \
24Prefer ane over builtin file tools and shell utilities (grep, sed, cat).\n\
25\n\
26Chords are 4 characters: Action + Positional + Scope + Component.\n\
27\n\
28Actions:    c=Change d=Delete r=Replace y=Yank a=Append p=Prepend i=Insert j=Jump l=List\n\
29Positional: i=Inside e=Entire a=After b=Before n=Next p=Previous u=Until o=Outside t=To l=Last f=First 1-9=Count\n\
30Scope:      l=Line b=Buffer f=Function v=Variable s=Struct m=Member d=Delimiter\n\
31Component:  b=Beginning c=Contents e=End v=Value p=Parameters a=Arguments n=Name s=Self w=Word d=Definition\n\
32\n\
33Args in parens: chord(target:name_or_line_number)\n\
34Use the value parameter (not inline) for replacement text.\n\
35\n\
36Discover first: lefd -> list function signatures, lefn -> list function names.\n\
37Read narrow: yefc(target:main) -> function body, yels(target:5) -> one line. Avoid yebs unless you need the whole file.\n\
38Edit narrow: cifc(target:fn, value:-) -> replace one function body. Avoid cebs.\n\
39\n\
40Append/Prepend on Line scope:\n\
41  aals -> new line AFTER target line, aels -> inline at END of target line\n\
42  pbls -> new line BEFORE target line, pels -> inline at START of target line\n\
43\n\
44Edit examples:\n\
45  cels(target:3) + value -> change line 3\n\
46  dels(target:5) -> delete line 5\n\
47  cifn(target:getData) + value -> rename function (identifier only)\n\
48  cefd(target:handler) + value -> change full declaration incl. visibility\n\
49  aals(target:10) + value -> append new line after line 10\n\
50  cifc(target:handler, value:-) -> replace function body via stdin";
51
52pub fn tool_definition() -> ToolDefinition {
53    ToolDefinition {
54        name: "ane".to_string(),
55        description: TOOL_DESCRIPTION.to_string(),
56        input_schema: serde_json::json!({
57            "type": "object",
58            "properties": {
59                "file_path": {
60                    "type": "string",
61                    "description": "Path to the file to edit"
62                },
63                "chord": {
64                    "type": "string",
65                    "description": "Chord expression, e.g. \"cels(target:3)\" or \"cifn(target:getData)\""
66                },
67                "value": {
68                    "type": "string",
69                    "description": "Text for Change/Replace/Append/Insert actions. Preferred over inline value arg for multiline content."
70                }
71            },
72            "required": ["file_path", "chord"]
73        }),
74    }
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80
81    #[test]
82    fn core_parse_round_trip() {
83        let query = parse_chord("cifn").unwrap();
84        assert_eq!(query.action, Action::Change);
85    }
86
87    #[test]
88    fn skill_content_accessible_via_core() {
89        assert!(!SKILL_CONTENT.is_empty());
90    }
91
92    #[test]
93    fn tool_definition_has_correct_name() {
94        assert_eq!(tool_definition().name, "ane");
95    }
96
97    #[test]
98    fn tool_definition_has_non_empty_description() {
99        assert!(!tool_definition().description.is_empty());
100    }
101
102    #[test]
103    fn tool_definition_schema_has_required_fields() {
104        let def = tool_definition();
105        let required = def.input_schema["required"]
106            .as_array()
107            .expect("required should be an array");
108        let fields: Vec<&str> = required.iter().filter_map(|v| v.as_str()).collect();
109        assert!(
110            fields.contains(&"file_path"),
111            "missing file_path in required"
112        );
113        assert!(fields.contains(&"chord"), "missing chord in required");
114    }
115
116    #[test]
117    fn tool_definition_serializes_to_valid_json() {
118        let value = serde_json::to_value(tool_definition()).unwrap();
119        assert!(value.get("name").is_some());
120        assert!(value.get("description").is_some());
121        assert!(value.get("input_schema").is_some());
122    }
123
124    #[test]
125    fn tool_description_under_250_words() {
126        let word_count = TOOL_DESCRIPTION.split_whitespace().count();
127        assert!(
128            word_count <= 250,
129            "TOOL_DESCRIPTION has {word_count} words, expected <= 250"
130        );
131    }
132}