omni_dev/data/
mod.rs

1//! Data processing and serialization
2
3use crate::git::{CommitInfo, RemoteInfo};
4use serde::{Deserialize, Serialize};
5
6pub mod amendments;
7pub mod yaml;
8
9pub use amendments::*;
10pub use yaml::*;
11
12/// Complete repository view output structure
13#[derive(Debug, Serialize, Deserialize)]
14pub struct RepositoryView {
15    /// Explanation of field meanings and structure
16    pub explanation: FieldExplanation,
17    /// Working directory status information
18    pub working_directory: WorkingDirectoryInfo,
19    /// List of remote repositories and their main branches
20    pub remotes: Vec<RemoteInfo>,
21    /// Branch information (only present when using branch commands)
22    #[serde(skip_serializing_if = "Option::is_none")]
23    pub branch_info: Option<BranchInfo>,
24    /// Pull request template content (only present in branch commands when template exists)
25    #[serde(skip_serializing_if = "Option::is_none")]
26    pub pr_template: Option<String>,
27    /// Pull requests created from the current branch (only present in branch commands)
28    #[serde(skip_serializing_if = "Option::is_none")]
29    pub branch_prs: Option<Vec<PullRequest>>,
30    /// List of analyzed commits with metadata and analysis
31    pub commits: Vec<CommitInfo>,
32}
33
34/// Field explanation for the YAML output
35#[derive(Debug, Serialize, Deserialize)]
36pub struct FieldExplanation {
37    /// Descriptive text explaining the overall structure
38    pub text: String,
39    /// Documentation for individual fields in the output
40    pub fields: Vec<FieldDocumentation>,
41}
42
43/// Individual field documentation
44#[derive(Debug, Serialize, Deserialize)]
45pub struct FieldDocumentation {
46    /// Name of the field being documented
47    pub name: String,
48    /// Descriptive text explaining what the field contains
49    pub text: String,
50    /// Git command that corresponds to this field (if applicable)
51    #[serde(skip_serializing_if = "Option::is_none")]
52    pub command: Option<String>,
53    /// Whether this field is present in the current output
54    pub present: bool,
55}
56
57/// Working directory information
58#[derive(Debug, Serialize, Deserialize)]
59pub struct WorkingDirectoryInfo {
60    /// Whether the working directory has no changes
61    pub clean: bool,
62    /// List of files with uncommitted changes
63    pub untracked_changes: Vec<FileStatusInfo>,
64}
65
66/// File status information for working directory
67#[derive(Debug, Serialize, Deserialize)]
68pub struct FileStatusInfo {
69    /// Git status flags (e.g., "AM", "??", "M ")
70    pub status: String,
71    /// Path to the file relative to repository root
72    pub file: String,
73}
74
75/// Branch information for branch-specific commands
76#[derive(Debug, Serialize, Deserialize)]
77pub struct BranchInfo {
78    /// Current branch name
79    pub branch: String,
80}
81
82/// Pull request information
83#[derive(Debug, Clone, Serialize, Deserialize)]
84pub struct PullRequest {
85    /// PR number
86    pub number: u64,
87    /// PR title
88    pub title: String,
89    /// PR state (open, closed, merged)
90    pub state: String,
91    /// PR URL
92    pub url: String,
93    /// PR description/body content
94    pub body: String,
95}
96
97impl RepositoryView {
98    /// Update the present field for all field documentation entries based on actual data
99    pub fn update_field_presence(&mut self) {
100        for field in &mut self.explanation.fields {
101            field.present = match field.name.as_str() {
102                "working_directory.clean" => true,             // Always present
103                "working_directory.untracked_changes" => true, // Always present
104                "remotes" => true,                             // Always present
105                "commits[].hash" => !self.commits.is_empty(),
106                "commits[].author" => !self.commits.is_empty(),
107                "commits[].date" => !self.commits.is_empty(),
108                "commits[].original_message" => !self.commits.is_empty(),
109                "commits[].in_main_branches" => !self.commits.is_empty(),
110                "commits[].analysis.detected_type" => !self.commits.is_empty(),
111                "commits[].analysis.detected_scope" => !self.commits.is_empty(),
112                "commits[].analysis.proposed_message" => !self.commits.is_empty(),
113                "commits[].analysis.file_changes.total_files" => !self.commits.is_empty(),
114                "commits[].analysis.file_changes.files_added" => !self.commits.is_empty(),
115                "commits[].analysis.file_changes.files_deleted" => !self.commits.is_empty(),
116                "commits[].analysis.file_changes.file_list" => !self.commits.is_empty(),
117                "commits[].analysis.diff_summary" => !self.commits.is_empty(),
118                "commits[].analysis.diff_content" => !self.commits.is_empty(),
119                "branch_info.branch" => self.branch_info.is_some(),
120                "pr_template" => self.pr_template.is_some(),
121                "branch_prs" => self.branch_prs.is_some(),
122                "branch_prs[].number" => {
123                    self.branch_prs.as_ref().is_some_and(|prs| !prs.is_empty())
124                }
125                "branch_prs[].title" => self.branch_prs.as_ref().is_some_and(|prs| !prs.is_empty()),
126                "branch_prs[].state" => self.branch_prs.as_ref().is_some_and(|prs| !prs.is_empty()),
127                "branch_prs[].url" => self.branch_prs.as_ref().is_some_and(|prs| !prs.is_empty()),
128                "branch_prs[].body" => self.branch_prs.as_ref().is_some_and(|prs| !prs.is_empty()),
129                _ => false, // Unknown fields are not present
130            }
131        }
132    }
133}
134
135impl Default for FieldExplanation {
136    /// Create default field explanation
137    fn default() -> Self {
138        Self {
139            text: [
140                "Field documentation for the YAML output format. Each entry describes the purpose and content of fields returned by the view command.",
141                "",
142                "Field structure:",
143                "- name: Specifies the YAML field path",
144                "- text: Provides a description of what the field contains",
145                "- command: Shows the corresponding command used to obtain that data (if applicable)",
146                "- present: Indicates whether this field is present in the current output",
147                "",
148                "IMPORTANT FOR AI ASSISTANTS: If a field shows present=true, it is guaranteed to be somewhere in this document. AI assistants should search the entire document thoroughly for any field marked as present=true, as it is definitely included in the output."
149            ].join("\n"),
150            fields: vec![
151                FieldDocumentation {
152                    name: "working_directory.clean".to_string(),
153                    text: "Boolean indicating if the working directory has no uncommitted changes".to_string(),
154                    command: Some("git status".to_string()),
155                    present: false, // Will be set dynamically when creating output
156                },
157                FieldDocumentation {
158                    name: "working_directory.untracked_changes".to_string(),
159                    text: "Array of files with uncommitted changes, showing git status and file path".to_string(),
160                    command: Some("git status --porcelain".to_string()),
161                    present: false,
162                },
163                FieldDocumentation {
164                    name: "remotes".to_string(),
165                    text: "Array of git remotes with their URLs and detected main branch names".to_string(),
166                    command: Some("git remote -v".to_string()),
167                    present: false,
168                },
169                FieldDocumentation {
170                    name: "commits[].hash".to_string(),
171                    text: "Full SHA-1 hash of the commit".to_string(),
172                    command: Some("git log --format=%H".to_string()),
173                    present: false,
174                },
175                FieldDocumentation {
176                    name: "commits[].author".to_string(),
177                    text: "Commit author name and email address".to_string(),
178                    command: Some("git log --format=%an <%ae>".to_string()),
179                    present: false,
180                },
181                FieldDocumentation {
182                    name: "commits[].date".to_string(),
183                    text: "Commit date in ISO format with timezone".to_string(),
184                    command: Some("git log --format=%aI".to_string()),
185                    present: false,
186                },
187                FieldDocumentation {
188                    name: "commits[].original_message".to_string(),
189                    text: "The original commit message as written by the author".to_string(),
190                    command: Some("git log --format=%B".to_string()),
191                    present: false,
192                },
193                FieldDocumentation {
194                    name: "commits[].in_main_branches".to_string(),
195                    text: "Array of remote main branches that contain this commit (empty if not pushed)".to_string(),
196                    command: Some("git branch -r --contains <commit>".to_string()),
197                    present: false,
198                },
199                FieldDocumentation {
200                    name: "commits[].analysis.detected_type".to_string(),
201                    text: "Automatically detected conventional commit type (feat, fix, docs, test, chore, etc.)".to_string(),
202                    command: None,
203                    present: false,
204                },
205                FieldDocumentation {
206                    name: "commits[].analysis.detected_scope".to_string(),
207                    text: "Automatically detected scope based on file paths (commands, config, tests, etc.)".to_string(),
208                    command: None,
209                    present: false,
210                },
211                FieldDocumentation {
212                    name: "commits[].analysis.proposed_message".to_string(),
213                    text: "AI-generated conventional commit message based on file changes".to_string(),
214                    command: None,
215                    present: false,
216                },
217                FieldDocumentation {
218                    name: "commits[].analysis.file_changes.total_files".to_string(),
219                    text: "Total number of files modified in this commit".to_string(),
220                    command: Some("git show --name-only <commit>".to_string()),
221                    present: false,
222                },
223                FieldDocumentation {
224                    name: "commits[].analysis.file_changes.files_added".to_string(),
225                    text: "Number of new files added in this commit".to_string(),
226                    command: Some("git show --name-status <commit> | grep '^A'".to_string()),
227                    present: false,
228                },
229                FieldDocumentation {
230                    name: "commits[].analysis.file_changes.files_deleted".to_string(),
231                    text: "Number of files deleted in this commit".to_string(),
232                    command: Some("git show --name-status <commit> | grep '^D'".to_string()),
233                    present: false,
234                },
235                FieldDocumentation {
236                    name: "commits[].analysis.file_changes.file_list".to_string(),
237                    text: "Array of files changed with their git status (M=modified, A=added, D=deleted)".to_string(),
238                    command: Some("git show --name-status <commit>".to_string()),
239                    present: false,
240                },
241                FieldDocumentation {
242                    name: "commits[].analysis.diff_summary".to_string(),
243                    text: "Git diff --stat output showing lines changed per file".to_string(),
244                    command: Some("git show --stat <commit>".to_string()),
245                    present: false,
246                },
247                FieldDocumentation {
248                    name: "commits[].analysis.diff_content".to_string(),
249                    text: "Full diff content showing line-by-line changes with added, removed, and context lines".to_string(),
250                    command: Some("git show <commit>".to_string()),
251                    present: false,
252                },
253                FieldDocumentation {
254                    name: "branch_info.branch".to_string(),
255                    text: "Current branch name (only present in branch commands)".to_string(),
256                    command: Some("git branch --show-current".to_string()),
257                    present: false,
258                },
259                FieldDocumentation {
260                    name: "pr_template".to_string(),
261                    text: "Pull request template content from .github/pull_request_template.md (only present in branch commands when file exists)".to_string(),
262                    command: None,
263                    present: false,
264                },
265                FieldDocumentation {
266                    name: "branch_prs".to_string(),
267                    text: "Pull requests created from the current branch (only present in branch commands)".to_string(),
268                    command: None,
269                    present: false,
270                },
271                FieldDocumentation {
272                    name: "branch_prs[].number".to_string(),
273                    text: "Pull request number".to_string(),
274                    command: None,
275                    present: false,
276                },
277                FieldDocumentation {
278                    name: "branch_prs[].title".to_string(),
279                    text: "Pull request title".to_string(),
280                    command: None,
281                    present: false,
282                },
283                FieldDocumentation {
284                    name: "branch_prs[].state".to_string(),
285                    text: "Pull request state (open, closed, merged)".to_string(),
286                    command: None,
287                    present: false,
288                },
289                FieldDocumentation {
290                    name: "branch_prs[].url".to_string(),
291                    text: "Pull request URL".to_string(),
292                    command: None,
293                    present: false,
294                },
295                FieldDocumentation {
296                    name: "branch_prs[].body".to_string(),
297                    text: "Pull request description/body content".to_string(),
298                    command: None,
299                    present: false,
300                },
301            ],
302        }
303    }
304}