Skip to main content

scope_engine/
api.rs

1use schemars::JsonSchema;
2use serde::{Deserialize, Deserializer, Serialize};
3
4// ── Domain types ────────────────────────────────────────────
5
6#[derive(Debug, Clone, Deserialize, Serialize)]
7pub struct OpenProjectOutput {
8    pub status: String,
9    pub project_root: String,
10    #[serde(skip_serializing_if = "Option::is_none")]
11    pub detected_lsp_language: Option<String>,
12    #[serde(skip_serializing_if = "Option::is_none")]
13    pub lsp: Option<String>,
14}
15
16#[derive(Debug, Clone, Deserialize)]
17#[serde(deny_unknown_fields)]
18pub struct ReadCodeInput {
19    pub path: String,
20    pub anchor: String,
21    pub mode: ReadCodeMode,
22}
23
24#[derive(Debug, Clone, Copy, Deserialize, Serialize, JsonSchema, PartialEq, Eq)]
25#[serde(rename_all = "lowercase")]
26pub enum ReadCodeMode {
27    Around,
28    Full,
29}
30
31#[derive(Debug, Clone, Deserialize, Serialize)]
32pub struct ReadCodeOutput {
33    /// File content with per-line hash prefix: `line#hash|original_text\n`
34    pub content: String,
35}
36
37#[derive(Debug, Clone, Deserialize, Serialize)]
38pub struct SearchCodeOutput {
39    pub matches: Vec<SearchHit>,
40}
41
42#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
43pub struct SearchHit {
44    pub path: String,
45    pub hit: String,
46}
47
48#[derive(Debug, Clone, Copy, Default, Deserialize, Serialize, JsonSchema, PartialEq, Eq)]
49#[serde(rename_all = "lowercase")]
50pub enum SearchMode {
51    #[default]
52    Literal,
53    Regex,
54}
55
56#[derive(Debug, Clone, Copy, Default, Deserialize, Serialize, JsonSchema, PartialEq, Eq)]
57#[serde(rename_all = "lowercase")]
58pub enum SearchCase {
59    Sensitive,
60    Insensitive,
61    #[default]
62    Smart,
63}
64
65#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)]
66pub struct SearchCodeInput {
67    pub query: String,
68    #[serde(default)]
69    pub mode: SearchMode,
70    pub path: Option<String>,
71    #[serde(default, deserialize_with = "deserialize_string_list")]
72    pub include: Vec<String>,
73    #[serde(default, deserialize_with = "deserialize_string_list")]
74    pub exclude: Vec<String>,
75    #[serde(default, deserialize_with = "deserialize_string_list")]
76    pub types: Vec<String>,
77    #[serde(default, deserialize_with = "deserialize_string_list")]
78    pub type_not: Vec<String>,
79    #[serde(default, rename = "case")]
80    pub case_mode: SearchCase,
81    #[serde(default)]
82    pub word: bool,
83    #[serde(default)]
84    pub whole_line: bool,
85    #[serde(default)]
86    pub hidden: bool,
87    #[serde(default = "default_respect_ignore")]
88    pub respect_ignore: bool,
89    #[serde(default)]
90    pub follow: bool,
91    pub limit: Option<usize>,
92}
93
94impl Default for SearchCodeInput {
95    fn default() -> Self {
96        Self {
97            query: String::new(),
98            mode: SearchMode::default(),
99            path: None,
100            include: Vec::new(),
101            exclude: Vec::new(),
102            types: Vec::new(),
103            type_not: Vec::new(),
104            case_mode: SearchCase::default(),
105            word: false,
106            whole_line: false,
107            hidden: false,
108            respect_ignore: true,
109            follow: false,
110            limit: None,
111        }
112    }
113}
114
115fn default_respect_ignore() -> bool {
116    true
117}
118
119fn deserialize_string_list<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
120where
121    D: Deserializer<'de>,
122{
123    #[derive(Deserialize)]
124    #[serde(untagged)]
125    enum StringList {
126        One(String),
127        Many(Vec<String>),
128    }
129
130    let Some(value) = Option::<StringList>::deserialize(deserializer)? else {
131        return Ok(Vec::new());
132    };
133    Ok(match value {
134        StringList::One(value) => vec![value],
135        StringList::Many(values) => values,
136    })
137}
138
139#[derive(Debug, Clone, Deserialize)]
140pub struct EditCodeInput {
141    pub edits: Vec<StructuredEdit>,
142}
143
144/// Operation kind for a single structured edit.
145#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
146#[serde(rename_all = "lowercase")]
147pub enum EditOp {
148    Replace,
149    Append,
150    Prepend,
151}
152
153/// Content value: string, array of strings, or null.
154#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)]
155#[serde(untagged)]
156pub enum EditContent {
157    Lines(Vec<String>),
158    Text(String),
159}
160
161impl EditContent {
162    pub fn into_lines(self) -> Vec<String> {
163        match self {
164            EditContent::Lines(lines) => lines,
165            EditContent::Text(text) => text.lines().map(str::to_string).collect(),
166        }
167    }
168}
169
170/// One structured edit: op + path + line-hash anchors + optional content.
171#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)]
172pub struct StructuredEdit {
173    /// Relative file path from project root.
174    pub path: String,
175    /// Operation kind (auto-defaults to `append` for new files when omitted).
176    #[serde(default)]
177    pub op: Option<EditOp>,
178    /// `line#hash` anchor from read_code output.
179    /// For new files this can be omitted (defaults to `"1#"`).
180    #[serde(default)]
181    pub start: Option<String>,
182    /// `line#hash` end anchor (required for replace, ignored otherwise).
183    #[serde(skip_serializing_if = "Option::is_none")]
184    pub end: Option<String>,
185    /// Replacement/insertion content as string, array, or null.
186    /// `null` with `replace` means delete.
187    #[serde(skip_serializing_if = "Option::is_none")]
188    pub content: Option<EditContent>,
189}
190
191#[derive(Debug, Clone, Deserialize)]
192pub struct SourceResponsibilityInput {
193    pub path: String,
194}
195
196#[derive(Debug, Clone, Deserialize, Serialize)]
197pub struct SourceResponsibility {
198    pub is_responsible: bool,
199    pub path: String,
200    #[serde(skip_serializing_if = "Option::is_none")]
201    pub extension: Option<String>,
202    #[serde(skip_serializing_if = "Option::is_none")]
203    pub language: Option<String>,
204    pub reason: String,
205}
206
207// ── Propagation types ────────────────────────────────────────
208
209#[derive(Debug, Clone, Serialize, PartialEq)]
210#[serde(rename_all = "snake_case")]
211pub enum PropagationSource {
212    Lsp,
213    OpenEnded,
214}
215
216#[derive(Debug, Clone, Serialize)]
217pub struct EditCodeOutput {
218    pub propagation_results: Vec<PropagationResult>,
219    pub applied_summary: AppliedStructuredEditSummary,
220}
221
222#[derive(Debug, Clone, Serialize)]
223pub struct PropagationResult {
224    pub selector: String,
225    pub reason: String,
226    pub source: PropagationSource,
227    #[serde(skip_serializing_if = "Option::is_none")]
228    pub lsp_references: Option<Vec<(String, usize, String)>>,
229    #[serde(skip_serializing_if = "Option::is_none")]
230    pub diff_summary: Option<String>,
231    #[serde(skip_serializing_if = "Option::is_none")]
232    pub file_snippet: Option<String>,
233    #[serde(skip_serializing_if = "Option::is_none")]
234    pub project_files: Option<Vec<String>>,
235}
236
237#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
238#[serde(rename_all = "snake_case")]
239pub enum AppliedStructuredEditOperation {
240    Add,
241    Update,
242}
243
244#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
245pub struct AppliedStructuredEditFile {
246    pub path: String,
247    pub operation: AppliedStructuredEditOperation,
248    pub added_lines: usize,
249    pub removed_lines: usize,
250    pub original_content: String,
251    pub new_content: String,
252}
253
254#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
255pub struct AppliedStructuredEditSummary {
256    pub files: Vec<AppliedStructuredEditFile>,
257}
258
259#[derive(Debug, Clone, Serialize)]
260pub struct ReviewBatch {
261    pub review: Option<ReviewEvent>,
262    pub reviews: Vec<ReviewEvent>,
263    pub returned: usize,
264    pub remaining: usize,
265}
266
267#[derive(Debug, Clone, Serialize)]
268pub struct Reference {
269    pub selector: String,
270    pub line: usize,
271    pub context: String,
272}
273
274#[derive(Debug, Clone, Serialize)]
275#[serde(tag = "type")]
276#[serde(rename_all = "snake_case")]
277pub enum ReviewEvent {
278    KnownReferences {
279        modified_symbol: String,
280        change_summary: String,
281        references: Vec<Reference>,
282        file_snippet: String,
283    },
284    InvestigateImpact {
285        modified_symbol: String,
286        change_summary: String,
287        diff_summary: String,
288        file_snippet: String,
289        project_files: Vec<String>,
290    },
291}