1use std::collections::HashMap;
2
3#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
6#[serde(rename_all = "camelCase")]
7pub struct ContextEngineRecord {
8 #[serde(default = "default_context_enabled")]
9 pub enabled: bool,
10 #[serde(default = "default_context_auto_retrieve")]
11 pub auto_retrieve: bool,
12 #[serde(default = "default_max_rule_results")]
13 pub max_rule_results: i32,
14 #[serde(default = "default_rule_token_budget")]
15 pub rule_token_budget: i32,
16 #[serde(default)]
17 pub allow_hosted_embeddings: bool,
18 #[serde(default)]
21 pub semantic_embedding: bool,
22 #[serde(default)]
24 pub embedding_provider_url: Option<String>,
25 #[serde(default)]
33 pub embedding_provider_key: Option<String>,
34 #[serde(default)]
36 pub embedding_model: Option<String>,
37 #[serde(default)]
39 pub embedding_dim: Option<usize>,
40}
41
42const fn default_context_enabled() -> bool {
43 true
44}
45const fn default_context_auto_retrieve() -> bool {
46 true
47}
48const fn default_max_rule_results() -> i32 {
49 4
50}
51const fn default_rule_token_budget() -> i32 {
52 1500
53}
54
55impl Default for ContextEngineRecord {
56 fn default() -> Self {
57 Self {
58 enabled: true,
59 auto_retrieve: true,
60 max_rule_results: 4,
61 rule_token_budget: 1500,
62 allow_hosted_embeddings: false,
63 semantic_embedding: false,
64 embedding_provider_url: None,
65 embedding_provider_key: None,
66 embedding_model: None,
67 embedding_dim: None,
68 }
69 }
70}
71
72#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
73#[serde(rename_all = "camelCase")]
74pub struct ReviewEngineRecord {
75 #[serde(default)]
76 pub multi_perspective: bool,
77 #[serde(default = "default_past_verdict_recall")]
82 pub past_verdict_recall: bool,
83 #[serde(default = "default_true")]
88 pub self_check_enabled: bool,
89 #[serde(default = "default_true")]
93 pub review_summary_enabled: bool,
94 #[serde(default = "default_true")]
103 pub hunk_line_resolution: bool,
104 #[serde(default)]
121 pub rule_applicability_judge: bool,
122}
123
124const fn default_past_verdict_recall() -> bool {
125 true
126}
127const fn default_true() -> bool {
128 true
129}
130
131impl Default for ReviewEngineRecord {
132 fn default() -> Self {
133 Self {
134 multi_perspective: false,
135 past_verdict_recall: default_past_verdict_recall(),
136 self_check_enabled: default_true(),
137 review_summary_enabled: default_true(),
138 hunk_line_resolution: default_true(),
139 rule_applicability_judge: false,
140 }
141 }
142}
143
144#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
148#[serde(rename_all = "camelCase")]
149pub struct FileIntent {
150 pub file: String,
151 pub intent: String,
152}
153
154#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
159#[serde(rename_all = "camelCase")]
160pub struct ReviewSummary {
161 pub one_line_summary: String,
162 pub walkthrough_by_file: Vec<FileIntent>,
163 pub blocking_count: u32,
164 pub non_blocking_count: u32,
165}
166
167#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
168#[serde(rename_all = "camelCase")]
169pub struct AppSettingsRecord {
170 #[serde(default)]
171 pub proxy_enabled: bool,
172 #[serde(default = "default_proxy_port")]
173 pub proxy_port: i32,
174 #[serde(default = "default_language")]
175 pub language: String,
176 #[serde(default = "default_theme")]
177 pub theme: String,
178 #[serde(default)]
179 pub sound_notifications: bool,
180 #[serde(default)]
181 pub default_shell: Option<String>,
182 #[serde(default = "default_workspace")]
183 pub default_workspace: String,
184 #[serde(default)]
185 pub shortcuts: HashMap<String, String>,
186 #[serde(default)]
187 pub context_engine: ContextEngineRecord,
188 #[serde(default)]
189 pub review_engine: ReviewEngineRecord,
190 #[serde(default = "default_true")]
194 pub hints_mcp: bool,
195
196 #[serde(default = "default_fix_default_mode", rename = "fixDefaultMode")]
199 pub fix_default_mode: String,
200
201 #[serde(default, rename = "syncAuto")]
204 pub sync_auto: bool,
205
206 #[serde(default, rename = "cloudAutoLogin")]
210 pub cloud_auto_login: bool,
211}
212
213const fn default_proxy_port() -> i32 {
214 4000
215}
216fn default_language() -> String {
217 "en".into()
218}
219fn default_theme() -> String {
220 "dark".into()
221}
222fn default_workspace() -> String {
223 "~/projects".into()
224}
225fn default_fix_default_mode() -> String {
226 "preview".into()
227}
228impl Default for AppSettingsRecord {
229 fn default() -> Self {
230 Self {
231 proxy_enabled: false,
232 proxy_port: default_proxy_port(),
233 language: default_language(),
234 theme: default_theme(),
235 sound_notifications: false,
236 default_shell: None,
237 default_workspace: default_workspace(),
238 shortcuts: HashMap::new(),
239 context_engine: ContextEngineRecord::default(),
240 review_engine: ReviewEngineRecord::default(),
241 hints_mcp: true,
242 fix_default_mode: default_fix_default_mode(),
243 sync_auto: false,
244 cloud_auto_login: false,
245 }
246 }
247}
248
249#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
252pub struct RuntimeReadyEvent {
253 pub runtime: String,
254}
255
256#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
259#[serde(rename_all = "camelCase")]
260pub struct ProjectRecord {
261 pub id: String,
262 pub name: String,
263 pub path: String,
264 pub git_branch: Option<String>,
265 pub active_sessions: i32,
266 pub total_sessions: Option<i32>,
267 pub created_at: String,
268}
269
270#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
271#[serde(rename_all = "camelCase")]
272pub struct AddProjectInput {
273 pub path: String,
274}
275
276#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
277#[serde(rename_all = "camelCase")]
278pub struct RemoveProjectInput {
279 pub id: String,
280}
281
282#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
285#[serde(rename_all = "camelCase")]
286pub struct ProviderRecord {
287 pub id: String,
288 pub name: String,
289 pub base_url: String,
290 pub api_key: Option<String>,
291 pub model_mapping: HashMap<String, String>,
292 pub is_active: bool,
293 pub created_at: String,
294 pub updated_at: String,
295}
296
297#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
298pub struct ProviderAddInput {
299 pub name: String,
300 pub base_url: String,
301 pub model_mapping: HashMap<String, String>,
302}
303
304#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
305pub struct ProviderUpdateInput {
306 pub id: String,
307 pub name: Option<String>,
308 pub base_url: Option<String>,
309 pub model_mapping: Option<HashMap<String, String>>,
310}
311
312#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
313pub struct ProviderRemoveInput {
314 pub id: String,
315}
316
317#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
318#[serde(rename_all = "camelCase")]
319pub struct ProviderSetActiveInput {
320 pub id: String,
321 pub is_active: bool,
322}
323
324#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
327#[serde(rename_all = "camelCase")]
328pub struct SkillRecord {
329 pub id: String,
330 pub name: String,
331 pub source: String,
332 pub directory: String,
333 pub version: String,
334 pub description: String,
335 pub r#type: String,
336 pub engines: Vec<String>,
337 pub tags: Vec<String>,
338 pub trigger: Option<String>,
339 pub check_prompt: Option<String>,
340 pub repo_owner: Option<String>,
341 pub repo_name: Option<String>,
342 pub repo_branch: Option<String>,
343 pub readme_url: Option<String>,
344 pub enabled_for_codex: bool,
345 pub enabled_for_claude: bool,
346 pub enabled_for_gemini: bool,
347 pub enabled_for_cursor: bool,
348 pub installed_at: String,
349 pub updated_at: String,
350 pub enforcement: Option<String>,
351 #[serde(default = "default_origin")]
354 pub origin: String,
355}
356
357fn default_origin() -> String {
358 "manual".to_owned()
359}
360
361#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
362pub struct InstallSkillInput {
363 pub owner: String,
364 pub repo: String,
365 pub branch: String,
366 pub directory: String,
367}
368
369#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
370pub struct RemoveSkillInput {
371 pub id: String,
372}
373
374#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
375pub struct ToggleSkillEngineInput {
376 pub id: String,
377 pub engine: String,
378 pub enabled: bool,
379}
380
381#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
382pub struct DiscoverSkillsInput {
383 pub owner: String,
384 pub repo: String,
385 pub branch: Option<String>,
386}
387
388#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
389#[serde(rename_all = "camelCase")]
390pub struct DiscoveredSkillRecord {
391 pub name: String,
392 pub description: String,
393 pub r#type: String,
394 pub engines: Vec<String>,
395 pub tags: Vec<String>,
396 pub version: String,
397 pub directory: String,
398 pub repo_owner: String,
399 pub repo_name: String,
400 pub repo_branch: String,
401 pub installed: bool,
402}
403
404#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
405pub struct CreateLocalSkillInput {
406 pub name: String,
407 pub engines: Option<Vec<String>>,
408 pub tags: Option<Vec<String>>,
409 pub description: Option<String>,
410 pub r#type: Option<String>,
411 pub trigger: Option<String>,
412 pub check_prompt: Option<String>,
413 pub content: Option<String>,
414}
415
416#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
424#[serde(rename_all = "camelCase")]
425pub struct RememberRuleInput {
426 pub title: String,
428 pub body: String,
431 #[serde(default)]
434 pub file_patterns: Option<Vec<String>>,
435 pub bad_code: Option<String>,
437 pub good_code: Option<String>,
439 pub severity: Option<String>,
441 #[serde(default)]
445 pub origin: Option<String>,
446}
447
448#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
449#[serde(rename_all = "camelCase")]
450pub struct SkillRepoRecord {
451 pub id: String,
452 pub owner: String,
453 pub name: String,
454 pub branch: String,
455 pub enabled: bool,
456 pub created_at: String,
457}
458
459#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
460pub struct SkillRepoAddInput {
461 pub owner: String,
462 pub name: String,
463 pub branch: Option<String>,
464}
465
466#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
467pub struct SkillRepoRemoveInput {
468 pub id: String,
469}
470
471#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
474#[serde(rename_all = "camelCase")]
475pub struct UpdateConfidenceInput {
476 pub skill_id: String,
477 pub signal: String,
479}
480
481#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
482#[serde(rename_all = "camelCase")]
483pub struct AddExampleInput {
484 pub skill_id: String,
485 pub bad_code: String,
486 pub good_code: String,
487 pub description: Option<String>,
488 pub source: Option<String>,
489}
490
491#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
492#[serde(rename_all = "camelCase")]
493pub struct ListExamplesInput {
494 pub skill_id: String,
495}
496
497#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
498pub struct RemoveExampleInput {
499 pub id: String,
500}
501
502#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
503#[serde(rename_all = "camelCase")]
504pub struct RuleExampleRecord {
505 pub id: String,
506 pub skill_id: String,
507 pub bad_code: String,
508 pub good_code: String,
509 pub description: Option<String>,
510 pub source: String,
511 pub created_at: String,
512}
513
514#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
517#[serde(rename_all = "camelCase")]
518pub struct GitStatusInput {
519 pub project_path: String,
520}
521
522#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
523#[serde(rename_all = "camelCase")]
524pub struct GitStatusRecord {
525 pub branch: Option<String>,
526 pub ahead: i32,
527 pub behind: i32,
528 pub files: Vec<GitFileStatusRecord>,
529}
530
531#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
532#[serde(rename_all = "camelCase")]
533pub struct GitFileStatusRecord {
534 pub path: String,
535 pub status: String,
536 pub additions: i32,
537 pub deletions: i32,
538}
539
540#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
541#[serde(rename_all = "camelCase")]
542pub struct GitBranchesInput {
543 pub project_path: String,
544}
545
546#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
547#[serde(rename_all = "camelCase")]
548pub struct GitBranchRecord {
549 pub name: String,
550 pub current: bool,
551 pub remote: Option<String>,
552}
553
554#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
555#[serde(rename_all = "camelCase")]
556pub struct GitDiffInput {
557 pub project_path: String,
558 pub staged: Option<bool>,
559 pub ref1: Option<String>,
560 pub ref2: Option<String>,
561}
562
563#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
564#[serde(rename_all = "camelCase")]
565pub struct DiffHunkRecord {
566 pub header: String,
567 pub body: String,
568}
569
570#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
571#[serde(rename_all = "camelCase")]
572pub struct DiffContentRecord {
573 pub file_path: String,
574 pub hunks: Vec<DiffHunkRecord>,
575}
576
577#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
578#[serde(rename_all = "camelCase")]
579pub struct GitCommitInput {
580 pub project_path: String,
581 pub message: String,
582 pub files: Option<Vec<String>>,
584}
585
586#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
587#[serde(rename_all = "camelCase")]
588pub struct GitPushInput {
589 pub project_path: String,
590}
591
592#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
593#[serde(rename_all = "camelCase")]
594pub struct GitCreatePRInput {
595 pub project_path: String,
596 pub title: String,
597 pub body: Option<String>,
598 pub base: Option<String>,
599}
600
601#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
602#[serde(rename_all = "camelCase")]
603pub struct GitCheckoutPRInput {
604 pub project_path: String,
605 pub pr_number: Option<i32>,
606}
607
608#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
609#[serde(rename_all = "camelCase")]
610pub struct GitPRResult {
611 pub url: Option<String>,
612}
613
614#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
615#[serde(rename_all = "camelCase")]
616pub struct EditorOpenInput {
617 pub project_path: String,
618 pub editor: Option<String>,
619 pub file_path: Option<String>,
620 pub line: Option<u32>,
621}
622
623#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
624#[serde(rename_all = "camelCase")]
625pub struct FilesSearchInput {
626 pub project_path: String,
627 pub query: String,
628 pub limit: Option<i32>,
629}
630
631#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
632#[serde(rename_all = "camelCase")]
633pub struct FilesReadInput {
634 pub project_path: String,
635 pub relative_path: String,
636 pub start_line: Option<i32>,
637 pub end_line: Option<i32>,
638 pub max_bytes: Option<i32>,
639}
640
641#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
642#[serde(rename_all = "camelCase")]
643pub struct FileSearchResult {
644 pub path: String,
645 pub relative_path: String,
646 pub is_directory: bool,
647}
648
649#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
650#[serde(rename_all = "camelCase")]
651pub struct FileReadRecord {
652 pub absolute_path: String,
653 pub relative_path: String,
654 pub content: String,
655 pub language: Option<String>,
656 pub line_count: i32,
657 pub truncated: bool,
658 pub sha256: Option<String>,
659}
660
661impl crate::domain::rule_view::RuleView for SkillRecord {
662 fn id(&self) -> &str {
663 &self.id
664 }
665 fn content(&self) -> &str {
666 &self.description
667 }
668 fn origin(&self) -> &str {
669 &self.origin
670 }
671 fn confidence(&self) -> Option<f64> {
672 None
673 }
674}
675
676#[cfg(test)]
677mod tests {
678 use super::*;
679 use crate::domain::rule_view::RuleView;
680
681 #[test]
682 fn skill_record_implements_rule_view() {
683 let s = SkillRecord {
684 id: "id1".into(),
685 name: "n".into(),
686 source: "s".into(),
687 directory: "d".into(),
688 version: "0".into(),
689 description: "body".into(),
690 r#type: "review_standard".into(),
691 engines: vec![],
692 tags: vec![],
693 trigger: None,
694 check_prompt: None,
695 repo_owner: None,
696 repo_name: None,
697 repo_branch: None,
698 readme_url: None,
699 enabled_for_codex: false,
700 enabled_for_claude: false,
701 enabled_for_gemini: false,
702 enabled_for_cursor: false,
703 installed_at: String::new(),
704 updated_at: String::new(),
705 enforcement: None,
706 origin: "pr_review".into(),
707 };
708 assert_eq!(s.id(), "id1");
709 assert_eq!(s.content(), "body");
710 assert_eq!(s.origin(), "pr_review");
711 assert_eq!(s.confidence(), None);
712 }
713
714 #[test]
715 fn hunk_line_resolution_defaults_on() {
716 assert!(ReviewEngineRecord::default().hunk_line_resolution);
719 }
720
721 #[test]
722 fn hunk_line_resolution_defaults_on_for_pre_phase4_configs() {
723 let rec: ReviewEngineRecord = serde_json::from_str("{}").unwrap();
727 assert!(
728 rec.hunk_line_resolution,
729 "missing key must default to true (sharpens fix patches on upgrade)"
730 );
731 let off: ReviewEngineRecord =
733 serde_json::from_str(r#"{"hunkLineResolution": false}"#).unwrap();
734 assert!(!off.hunk_line_resolution);
735 }
736
737 #[test]
738 fn rule_applicability_judge_defaults_off() {
739 assert!(!ReviewEngineRecord::default().rule_applicability_judge);
742 let rec: ReviewEngineRecord = serde_json::from_str("{}").unwrap();
743 assert!(
744 !rec.rule_applicability_judge,
745 "missing key must default to false (no extra LLM call unless opted in)"
746 );
747 let on: ReviewEngineRecord =
749 serde_json::from_str(r#"{"ruleApplicabilityJudge": true}"#).unwrap();
750 assert!(on.rule_applicability_judge);
751 }
752}