1pub mod detection;
7pub mod structure;
8
9use crate::cache::CacheManager;
10use anyhow::Result;
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
84 .path
85 .as_ref()
86 .map(|p| workspace_root.join(p))
87 .unwrap_or_else(|| workspace_root.clone());
88
89 if !target_path.exists() {
91 anyhow::bail!(
92 "Path '{}' does not exist in workspace",
93 opts.path.as_deref().unwrap_or(".")
94 );
95 }
96
97 let mut effective_opts = opts.clone();
99 if effective_opts.is_empty() {
100 effective_opts.structure = true;
102 effective_opts.file_types = true;
103 effective_opts.project_type = true;
104 effective_opts.framework = true;
105 effective_opts.entry_points = true;
106 effective_opts.test_layout = true;
107 effective_opts.config_files = true;
108 }
109
110 if opts.json {
111 generate_json_context(cache, &effective_opts, &target_path)
112 } else {
113 generate_text_context(cache, &effective_opts, &target_path)
114 }
115}
116
117fn generate_text_context(
119 cache: &CacheManager,
120 opts: &ContextOptions,
121 target_path: &std::path::Path,
122) -> Result<String> {
123 let mut sections = Vec::new();
124
125 let path_display = target_path
127 .strip_prefix(cache.workspace_root())
128 .unwrap_or(target_path)
129 .display();
130 sections.push(format!("# Project Context: {}\n", path_display));
131
132 if opts.project_type {
134 if let Ok(project_info) = detection::detect_project_type(cache, target_path) {
135 sections.push(format!("## Project Type\n{}\n", project_info));
136 }
137 }
138
139 if opts.entry_points {
141 if let Ok(entry_points) = detection::find_entry_points(target_path) {
142 if !entry_points.is_empty() {
143 sections.push(format!("## Entry Points\n{}\n", entry_points.join("\n")));
144 }
145 }
146 }
147
148 if opts.structure {
150 if let Ok(tree) = structure::generate_tree(target_path, opts.depth) {
151 sections.push(format!("## Directory Structure\n{}\n", tree));
152 }
153 }
154
155 if opts.file_types {
157 if let Ok(distribution) = detection::get_file_distribution(cache) {
158 sections.push(format!("## File Distribution\n{}\n", distribution));
159 }
160 }
161
162 if opts.test_layout {
164 if let Ok(test_info) = detection::detect_test_layout(target_path) {
165 sections.push(format!("## Test Organization\n{}\n", test_info));
166 }
167 }
168
169 if opts.framework {
171 if let Ok(frameworks) = detection::detect_frameworks(target_path) {
172 if !frameworks.is_empty() {
173 sections.push(format!("## Framework Detection\n{}\n", frameworks));
174 }
175 }
176 }
177
178 if opts.config_files {
180 if let Ok(configs) = detection::find_config_files(target_path) {
181 if !configs.is_empty() {
182 sections.push(format!("## Configuration Files\n{}\n", configs));
183 }
184 }
185 }
186
187 Ok(sections.join("\n"))
188}
189
190fn generate_json_context(
192 cache: &CacheManager,
193 opts: &ContextOptions,
194 target_path: &std::path::Path,
195) -> Result<String> {
196 use serde_json::json;
197
198 let mut context = json!({});
199
200 if opts.project_type {
201 if let Ok(project_type) = detection::detect_project_type_json(cache, target_path) {
202 context["project_type"] = project_type;
203 }
204 }
205
206 if opts.entry_points {
207 if let Ok(entry_points) = detection::find_entry_points_json(target_path) {
208 context["entry_points"] = entry_points;
209 }
210 }
211
212 if opts.structure {
213 if let Ok(tree) = structure::generate_tree_json(target_path, opts.depth) {
214 context["structure"] = tree;
215 }
216 }
217
218 if opts.file_types {
219 if let Ok(distribution) = detection::get_file_distribution_json(cache) {
220 context["file_distribution"] = distribution;
221 }
222 }
223
224 if opts.test_layout {
225 if let Ok(test_layout) = detection::detect_test_layout_json(target_path) {
226 context["test_layout"] = test_layout;
227 }
228 }
229
230 if opts.framework {
231 if let Ok(frameworks) = detection::detect_frameworks_json(target_path) {
232 context["frameworks"] = frameworks;
233 }
234 }
235
236 if opts.config_files {
237 if let Ok(configs) = detection::find_config_files_json(target_path) {
238 context["config_files"] = configs;
239 }
240 }
241
242 serde_json::to_string_pretty(&context).map_err(Into::into)
243}