vtcode_commons/
project.rs1use crate::utils::{extract_readme_excerpt, extract_toml_str};
4use std::fmt::Write as _;
5use std::path::{Path, PathBuf};
6use tokio::fs;
7
8pub struct ProjectOverview {
10 pub name: Option<String>,
11 pub version: Option<String>,
12 pub description: Option<String>,
13 pub readme_excerpt: Option<String>,
14 pub root: PathBuf,
15}
16
17impl ProjectOverview {
18 pub fn short_for_display(&self) -> String {
19 let mut out = String::new();
20 if let Some(name) = &self.name {
21 let _ = write!(out, "Project: {}", name);
22 }
23 if let Some(ver) = &self.version {
24 if !out.is_empty() {
25 out.push(' ');
26 }
27 let _ = write!(out, "v{}", ver);
28 }
29 if !out.is_empty() {
30 out.push('\n');
31 }
32 if let Some(desc) = &self.description {
33 out.push_str(desc);
34 out.push('\n');
35 }
36 let _ = write!(out, "Root: {}", self.root.display());
37 out
38 }
39
40 pub fn as_prompt_block(&self) -> String {
41 let mut s = String::new();
42 if let Some(name) = &self.name {
43 let _ = writeln!(s, "- Name: {}", name);
44 }
45 if let Some(ver) = &self.version {
46 let _ = writeln!(s, "- Version: {}", ver);
47 }
48 if let Some(desc) = &self.description {
49 let _ = writeln!(s, "- Description: {}", desc);
50 }
51 let _ = writeln!(s, "- Workspace Root: {}", self.root.display());
52 if let Some(excerpt) = &self.readme_excerpt {
53 s.push_str("- README Excerpt: \n");
54 s.push_str(excerpt);
55 if !excerpt.ends_with('\n') {
56 s.push('\n');
57 }
58 }
59 s
60 }
61}
62
63pub async fn build_project_overview(root: &Path) -> Option<ProjectOverview> {
65 let mut overview = ProjectOverview {
66 name: None,
67 version: None,
68 description: None,
69 readme_excerpt: None,
70 root: root.to_path_buf(),
71 };
72
73 let cargo_toml_path = root.join("Cargo.toml");
75 if let Ok(cargo_toml) = fs::read_to_string(&cargo_toml_path).await {
76 overview.name = extract_toml_str(&cargo_toml, "name");
77 overview.version = extract_toml_str(&cargo_toml, "version");
78 overview.description = extract_toml_str(&cargo_toml, "description");
79 }
80
81 let readme_path = root.join("README.md");
83 if let Ok(readme) = fs::read_to_string(&readme_path).await {
84 overview.readme_excerpt = Some(extract_readme_excerpt(&readme, 1200));
85 } else {
86 for alt in [
88 "QUICKSTART.md",
89 "user-context.md",
90 "docs/project/ROADMAP.md",
91 ] {
92 let path = root.join(alt);
93 if let Ok(txt) = fs::read_to_string(&path).await {
94 overview.readme_excerpt = Some(extract_readme_excerpt(&txt, 800));
95 break;
96 }
97 }
98 }
99
100 if overview.name.is_none() && overview.readme_excerpt.is_none() {
101 return None;
102 }
103 Some(overview)
104}