1pub mod detection;
7pub mod structure;
8
9use anyhow::Result;
10use crate::cache::CacheManager;
11
12#[derive(Debug, Clone)]
14pub struct ContextOptions {
15 pub structure: bool,
17
18 pub path: Option<String>,
20
21 pub file_types: bool,
23
24 pub project_type: bool,
26
27 pub framework: bool,
29
30 pub entry_points: bool,
32
33 pub test_layout: bool,
35
36 pub config_files: bool,
38
39 pub depth: usize,
41
42 pub json: bool,
44}
45
46impl Default for ContextOptions {
47 fn default() -> Self {
48 Self {
49 structure: true,
50 path: None,
51 file_types: true,
52 project_type: true,
53 framework: true,
54 entry_points: true,
55 test_layout: true,
56 config_files: true,
57 depth: 1,
58 json: false,
59 }
60 }
61}
62
63impl ContextOptions {
64 pub fn is_empty(&self) -> bool {
68 !self.structure
69 && !self.file_types
70 && !self.project_type
71 && !self.framework
72 && !self.entry_points
73 && !self.test_layout
74 && !self.config_files
75 }
76}
77
78pub fn generate_context(cache: &CacheManager, opts: &ContextOptions) -> Result<String> {
82 let workspace_root = cache.workspace_root();
83 let target_path = opts.path.as_ref()
84 .map(|p| workspace_root.join(p))
85 .unwrap_or_else(|| workspace_root.clone());
86
87 if !target_path.exists() {
89 anyhow::bail!("Path '{}' does not exist in workspace",
90 opts.path.as_deref().unwrap_or("."));
91 }
92
93 let mut effective_opts = opts.clone();
95 if effective_opts.is_empty() {
96 effective_opts.structure = true;
98 effective_opts.file_types = true;
99 effective_opts.project_type = true;
100 effective_opts.framework = true;
101 effective_opts.entry_points = true;
102 effective_opts.test_layout = true;
103 effective_opts.config_files = true;
104 }
105
106 if opts.json {
107 generate_json_context(cache, &effective_opts, &target_path)
108 } else {
109 generate_text_context(cache, &effective_opts, &target_path)
110 }
111}
112
113fn generate_text_context(
115 cache: &CacheManager,
116 opts: &ContextOptions,
117 target_path: &std::path::Path,
118) -> Result<String> {
119 let mut sections = Vec::new();
120
121 let path_display = target_path.strip_prefix(cache.workspace_root())
123 .unwrap_or(target_path)
124 .display();
125 sections.push(format!("# Project Context: {}\n", path_display));
126
127 if opts.project_type {
129 if let Ok(project_info) = detection::detect_project_type(cache, target_path) {
130 sections.push(format!("## Project Type\n{}\n", project_info));
131 }
132 }
133
134 if opts.entry_points {
136 if let Ok(entry_points) = detection::find_entry_points(target_path) {
137 if !entry_points.is_empty() {
138 sections.push(format!("## Entry Points\n{}\n", entry_points.join("\n")));
139 }
140 }
141 }
142
143 if opts.structure {
145 if let Ok(tree) = structure::generate_tree(target_path, opts.depth) {
146 sections.push(format!("## Directory Structure\n{}\n", tree));
147 }
148 }
149
150 if opts.file_types {
152 if let Ok(distribution) = detection::get_file_distribution(cache) {
153 sections.push(format!("## File Distribution\n{}\n", distribution));
154 }
155 }
156
157 if opts.test_layout {
159 if let Ok(test_info) = detection::detect_test_layout(target_path) {
160 sections.push(format!("## Test Organization\n{}\n", test_info));
161 }
162 }
163
164 if opts.framework {
166 if let Ok(frameworks) = detection::detect_frameworks(target_path) {
167 if !frameworks.is_empty() {
168 sections.push(format!("## Framework Detection\n{}\n", frameworks));
169 }
170 }
171 }
172
173 if opts.config_files {
175 if let Ok(configs) = detection::find_config_files(target_path) {
176 if !configs.is_empty() {
177 sections.push(format!("## Configuration Files\n{}\n", configs));
178 }
179 }
180 }
181
182 Ok(sections.join("\n"))
183}
184
185fn generate_json_context(
187 cache: &CacheManager,
188 opts: &ContextOptions,
189 target_path: &std::path::Path,
190) -> Result<String> {
191 use serde_json::json;
192
193 let mut context = json!({});
194
195 if opts.project_type {
196 if let Ok(project_type) = detection::detect_project_type_json(cache, target_path) {
197 context["project_type"] = project_type;
198 }
199 }
200
201 if opts.entry_points {
202 if let Ok(entry_points) = detection::find_entry_points_json(target_path) {
203 context["entry_points"] = entry_points;
204 }
205 }
206
207 if opts.structure {
208 if let Ok(tree) = structure::generate_tree_json(target_path, opts.depth) {
209 context["structure"] = tree;
210 }
211 }
212
213 if opts.file_types {
214 if let Ok(distribution) = detection::get_file_distribution_json(cache) {
215 context["file_distribution"] = distribution;
216 }
217 }
218
219 if opts.test_layout {
220 if let Ok(test_layout) = detection::detect_test_layout_json(target_path) {
221 context["test_layout"] = test_layout;
222 }
223 }
224
225 if opts.framework {
226 if let Ok(frameworks) = detection::detect_frameworks_json(target_path) {
227 context["frameworks"] = frameworks;
228 }
229 }
230
231 if opts.config_files {
232 if let Ok(configs) = detection::find_config_files_json(target_path) {
233 context["config_files"] = configs;
234 }
235 }
236
237 serde_json::to_string_pretty(&context).map_err(Into::into)
238}