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