ai_context_gen/
generator.rs1use anyhow::Result;
8use chrono::Utc;
9use std::fs;
10
11use crate::config::Config;
12use crate::parser::RustParser;
13use crate::scanner::{FileType, ScanResult};
14use crate::token_counter::{ContentPrioritizer, ContentSection};
15
16pub struct ContextGenerator {
38 config: Config,
39 prioritizer: ContentPrioritizer,
40}
41
42impl ContextGenerator {
43 pub fn new(config: Config) -> Self {
63 Self {
64 config,
65 prioritizer: ContentPrioritizer::new()
66 .expect("Failed to initialize content prioritizer"),
67 }
68 }
69
70 pub async fn generate_context(&self, scan_result: ScanResult) -> Result<()> {
111 let mut sections = Vec::new();
112
113 sections.push(self.create_metadata_section(&scan_result));
115
116 sections.push(self.create_structure_section(&scan_result));
118
119 sections.extend(self.create_markdown_sections(&scan_result));
121
122 sections.extend(self.create_rust_analysis_sections(&scan_result).await?);
124
125 sections.extend(self.create_source_code_sections(&scan_result));
127
128 let final_sections = self
130 .prioritizer
131 .prioritize_content(sections, self.config.max_tokens);
132
133 let context = self.format_context(final_sections);
135
136 fs::write(&self.config.output_file, context)?;
138
139 println!(
140 "Context generated successfully in: {}",
141 self.config.output_file
142 );
143 Ok(())
144 }
145
146 fn create_metadata_section(&self, scan_result: &ScanResult) -> ContentSection {
147 let mut content = String::new();
148 content.push_str("# Project Metadata\n\n");
149 content.push_str(&format!("**Name:** {}\n", scan_result.metadata.name));
150
151 if let Some(description) = &scan_result.metadata.description {
152 content.push_str(&format!("**Description:** {description}\n"));
153 }
154
155 if !scan_result.metadata.dependencies.is_empty() {
156 content.push_str("**Dependencies:**\n");
157 for dep in &scan_result.metadata.dependencies {
158 content.push_str(&format!("- {dep}\n"));
159 }
160 }
161
162 if let Some(rust_version) = &scan_result.metadata.rust_version {
163 content.push_str(&format!("**Version:** {rust_version}\n"));
164 }
165
166 content.push_str(&format!(
167 "**Total files:** {}\n",
168 scan_result.project_structure.total_files
169 ));
170 content.push_str(&format!(
171 "**Total size:** {} bytes\n\n",
172 scan_result.project_structure.total_size
173 ));
174
175 ContentSection {
176 title: "Project Metadata".to_string(),
177 content,
178 priority: 10,
179 truncated: false,
180 }
181 }
182
183 fn create_structure_section(&self, scan_result: &ScanResult) -> ContentSection {
184 let mut content = String::new();
185 content.push_str("# Project Structure\n\n");
186 content.push_str(&scan_result.project_structure.tree);
187 content.push('\n');
188
189 ContentSection {
190 title: "Project Structure".to_string(),
191 content,
192 priority: 9,
193 truncated: false,
194 }
195 }
196
197 fn create_markdown_sections(&self, scan_result: &ScanResult) -> Vec<ContentSection> {
198 let mut sections = Vec::new();
199
200 for file in &scan_result.files {
201 if matches!(file.file_type, FileType::Markdown) {
202 let mut content = String::new();
203 content.push_str(&format!(
204 "# Documentation: {}\n\n",
205 file.relative_path.display()
206 ));
207 content.push_str(&file.content);
208 content.push('\n');
209
210 sections.push(ContentSection {
211 title: format!("Documentation: {}", file.relative_path.display()),
212 content,
213 priority: 8,
214 truncated: false,
215 });
216 }
217 }
218
219 sections
220 }
221
222 async fn create_rust_analysis_sections(
223 &self,
224 scan_result: &ScanResult,
225 ) -> Result<Vec<ContentSection>> {
226 let mut sections = Vec::new();
227
228 for file in &scan_result.files {
229 if matches!(file.file_type, FileType::Rust) {
230 match RustParser::parse_rust_file(&file.path.to_string_lossy(), &file.content) {
231 Ok(analysis) => {
232 let mut content = String::new();
233 content.push_str(&format!(
234 "# Rust Analysis: {}\n\n",
235 file.relative_path.display()
236 ));
237
238 if !analysis.modules.is_empty() {
239 content.push_str("## Modules\n");
240 for module in &analysis.modules {
241 content.push_str(&format!(
242 "- **{}**: {}\n",
243 module.name, module.visibility
244 ));
245 }
246 content.push('\n');
247 }
248
249 if !analysis.functions.is_empty() {
250 content.push_str("## Functions\n");
251 for function in &analysis.functions {
252 let params = function.parameters.join(", ");
253 let return_type = function.return_type.as_deref().unwrap_or("()");
254 content.push_str(&format!(
255 "- **{}**({}) -> {} ({})\n",
256 function.name, params, return_type, function.visibility
257 ));
258 }
259 content.push('\n');
260 }
261
262 if !analysis.structs.is_empty() {
263 content.push_str("## Structs\n");
264 for struct_info in &analysis.structs {
265 content.push_str(&format!(
266 "- **{}**: {} fields ({})\n",
267 struct_info.name,
268 struct_info.fields.len(),
269 struct_info.visibility
270 ));
271 }
272 content.push('\n');
273 }
274
275 if !analysis.enums.is_empty() {
276 content.push_str("## Enums\n");
277 for enum_info in &analysis.enums {
278 content.push_str(&format!(
279 "- **{}**: {} variants ({})\n",
280 enum_info.name,
281 enum_info.variants.len(),
282 enum_info.visibility
283 ));
284 }
285 content.push('\n');
286 }
287
288 if !analysis.implementations.is_empty() {
289 content.push_str("## Implementations\n");
290 for impl_info in &analysis.implementations {
291 content.push_str(&format!(
292 "- **impl {}**: {} methods\n",
293 impl_info.target,
294 impl_info.methods.len()
295 ));
296 }
297 content.push('\n');
298 }
299
300 sections.push(ContentSection {
301 title: format!("Rust Analysis: {}", file.relative_path.display()),
302 content,
303 priority: 6,
304 truncated: false,
305 });
306 }
307 Err(e) => {
308 eprintln!(
309 "Warning: Failed to parse {}: {}",
310 file.relative_path.display(),
311 e
312 );
313 }
314 }
315 }
316 }
317
318 Ok(sections)
319 }
320
321 fn create_source_code_sections(&self, scan_result: &ScanResult) -> Vec<ContentSection> {
322 let mut sections = Vec::new();
323
324 for file in &scan_result.files {
325 let mut content = String::new();
326 content.push_str(&format!("# Source: {}\n\n", file.relative_path.display()));
327 content.push_str("```");
328
329 match file.file_type {
330 FileType::Rust => content.push_str("rust"),
331 FileType::Markdown => content.push_str("markdown"),
332 }
333
334 content.push('\n');
335 content.push_str(&file.content);
336 content.push_str("\n```\n\n");
337
338 sections.push(ContentSection {
339 title: format!("Source: {}", file.relative_path.display()),
340 content,
341 priority: 3,
342 truncated: false,
343 });
344 }
345
346 sections
347 }
348
349 fn format_context(&self, sections: Vec<ContentSection>) -> String {
350 let mut context = String::new();
351
352 context.push_str("# AI Context Generation Report\n\n");
354 context.push_str(&format!(
355 "Generated on: {}\n",
356 Utc::now().format("%Y-%m-%d %H:%M:%S UTC")
357 ));
358 context.push_str(&format!(
359 "Repository: {}\n",
360 self.config.repo_path.display()
361 ));
362 context.push_str(&format!("Max tokens: {}\n\n", self.config.max_tokens));
363
364 context.push_str("## Table of Contents\n\n");
366 for (i, section) in sections.iter().enumerate() {
367 context.push_str(&format!("{}. {}", i + 1, section.title));
368 if section.truncated {
369 context.push_str(" (truncated)");
370 }
371 context.push('\n');
372 }
373 context.push('\n');
374
375 for section in sections {
377 context.push_str("---\n\n");
378 context.push_str(§ion.content);
379 }
380
381 context
382 }
383}