1use std::path::PathBuf;
4
5use crate::error::Result;
6use crate::git::{self, CommitInfo};
7use crate::output::{CYAN, GRAY, GREEN, RESET, YELLOW};
8use crate::spec::Spec;
9use crate::state::StateManager;
10
11#[derive(Debug, Clone)]
13pub struct BranchContext {
14 pub branch_name: String,
16 pub spec: Option<Spec>,
18 pub spec_path: Option<PathBuf>,
20 pub commits: Vec<CommitInfo>,
22}
23
24#[derive(Debug, Clone)]
26pub enum BranchContextResult {
27 SuccessWithSpec(BranchContext),
29 SuccessNoSpec(BranchContext),
31 Error(String),
33}
34
35pub fn gather_branch_context(show_warning: bool) -> BranchContextResult {
37 let branch_name = match git::current_branch() {
38 Ok(b) => b,
39 Err(e) => {
40 return BranchContextResult::Error(format!("Failed to get current branch: {}", e))
41 }
42 };
43
44 let (spec, spec_path) = match find_spec_for_branch(&branch_name) {
46 Ok(Some((s, p))) => (Some(s), Some(p)),
47 Ok(None) => {
48 if show_warning {
49 println!(
50 "{YELLOW}Warning: No spec file found for branch '{}'{RESET}",
51 branch_name
52 );
53 println!("{GRAY}PR review will proceed with reduced context.{RESET}");
54 println!();
55 }
56 (None, None)
57 }
58 Err(e) => {
59 if show_warning {
60 println!("{YELLOW}Warning: Failed to load spec: {}{RESET}", e);
61 }
62 (None, None)
63 }
64 };
65
66 let commits = git::get_branch_commits(&branch_name).unwrap_or_default();
68
69 let context = BranchContext {
70 branch_name,
71 spec,
72 spec_path,
73 commits,
74 };
75
76 if context.spec.is_some() {
77 BranchContextResult::SuccessWithSpec(context)
78 } else {
79 BranchContextResult::SuccessNoSpec(context)
80 }
81}
82
83pub fn find_spec_for_branch(branch: &str) -> Result<Option<(Spec, PathBuf)>> {
85 let state_manager = StateManager::new()?;
86 let specs = state_manager.list_specs()?;
87
88 for spec_path in &specs {
90 if let Ok(spec) = Spec::load(spec_path) {
91 if spec.branch_name == branch {
92 return Ok(Some((spec, spec_path.clone())));
93 }
94 }
95 }
96
97 let branch_suffix = branch
100 .strip_prefix("feature/")
101 .or_else(|| branch.strip_prefix("feat/"))
102 .unwrap_or(branch);
103
104 for spec_path in &specs {
105 let filename = spec_path.file_stem().and_then(|s| s.to_str()).unwrap_or("");
106
107 if filename.ends_with(branch_suffix)
109 || filename.replace("spec-", "").contains(branch_suffix)
110 {
111 if let Ok(spec) = Spec::load(spec_path) {
112 return Ok(Some((spec, spec_path.clone())));
113 }
114 }
115 }
116
117 Ok(None)
118}
119
120pub fn print_branch_context(context: &BranchContext) {
122 println!("{CYAN}Branch:{RESET} {}", context.branch_name);
123
124 if context.spec.is_some() {
125 println!("{GREEN} ✓ Spec file loaded{RESET}");
126 } else {
127 println!("{YELLOW} ⚠ No spec file (reduced context){RESET}");
128 }
129
130 if !context.commits.is_empty() {
131 println!(
132 "{GRAY} {} commit{} on branch{RESET}",
133 context.commits.len(),
134 if context.commits.len() == 1 { "" } else { "s" }
135 );
136 }
137 println!();
138}