1use crate::git::{CommitInfo, RemoteInfo};
4use serde::{Deserialize, Serialize};
5
6pub mod amendments;
7pub mod yaml;
8
9pub use amendments::*;
10pub use yaml::*;
11
12#[derive(Debug, Serialize, Deserialize)]
14pub struct RepositoryView {
15 pub explanation: FieldExplanation,
17 pub working_directory: WorkingDirectoryInfo,
19 pub remotes: Vec<RemoteInfo>,
21 #[serde(skip_serializing_if = "Option::is_none")]
23 pub branch_info: Option<BranchInfo>,
24 #[serde(skip_serializing_if = "Option::is_none")]
26 pub pr_template: Option<String>,
27 #[serde(skip_serializing_if = "Option::is_none")]
29 pub branch_prs: Option<Vec<PullRequest>>,
30 pub commits: Vec<CommitInfo>,
32}
33
34#[derive(Debug, Serialize, Deserialize)]
36pub struct FieldExplanation {
37 pub text: String,
39 pub fields: Vec<FieldDocumentation>,
41}
42
43#[derive(Debug, Serialize, Deserialize)]
45pub struct FieldDocumentation {
46 pub name: String,
48 pub text: String,
50 #[serde(skip_serializing_if = "Option::is_none")]
52 pub command: Option<String>,
53 pub present: bool,
55}
56
57#[derive(Debug, Serialize, Deserialize)]
59pub struct WorkingDirectoryInfo {
60 pub clean: bool,
62 pub untracked_changes: Vec<FileStatusInfo>,
64}
65
66#[derive(Debug, Serialize, Deserialize)]
68pub struct FileStatusInfo {
69 pub status: String,
71 pub file: String,
73}
74
75#[derive(Debug, Serialize, Deserialize)]
77pub struct BranchInfo {
78 pub branch: String,
80}
81
82#[derive(Debug, Clone, Serialize, Deserialize)]
84pub struct PullRequest {
85 pub number: u64,
87 pub title: String,
89 pub state: String,
91 pub url: String,
93 pub body: String,
95}
96
97impl RepositoryView {
98 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, "working_directory.untracked_changes" => true, "remotes" => true, "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, }
131 }
132 }
133}
134
135impl Default for FieldExplanation {
136 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, },
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}