Skip to main content

vtcode_commons/
project.rs

1//! Project-related utilities and structures
2
3use crate::utils::{extract_readme_excerpt, extract_toml_str};
4use std::fmt::Write as _;
5use std::path::{Path, PathBuf};
6use tokio::fs;
7
8/// Lightweight project overview extracted from workspace files
9pub 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
63/// Build a minimal project overview from Cargo.toml and README.md
64pub 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    // Parse Cargo.toml (best-effort, no extra deps)
74    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    // Read README.md excerpt
82    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        // Fallback to alternatives
87        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}