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