返回 CodeWhale
project.rs
根目录 / crates / tui / src / tools / project.rs
1 //! Project mapping tool for understanding codebase structure.
2
3 use crate::utils::{is_key_file, project_tree, summarize_project};
4 use anyhow::Result;
5 use async_trait::async_trait;
6 use serde::Serialize;
7 use serde_json::{Value, json};
8
9 use super::spec::{
10 ApprovalRequirement, ToolCapability, ToolContext, ToolError, ToolResult, ToolSpec, optional_u64,
11 };
12
13 pub struct ProjectMapTool;
14
15 #[derive(Debug, Serialize)]
16 struct ProjectMap {
17 tree: String,
18 summary: String,
19 key_files: Vec<String>,
20 }
21
22 #[async_trait]
23 impl ToolSpec for ProjectMapTool {
24 fn name(&self) -> &'static str {
25 "project_map"
26 }
27
28 fn description(&self) -> &'static str {
29 "Get a high-level map of the project structure, including key files and a tree view."
30 }
31
32 fn input_schema(&self) -> Value {
33 json!({
34 "type": "object",
35 "properties": {
36 "max_depth": {
37 "type": "integer",
38 "description": "Maximum depth for the tree view (default: 3)."
39 }
40 }
41 })
42 }
43
44 fn capabilities(&self) -> Vec<ToolCapability> {
45 vec![ToolCapability::ReadOnly, ToolCapability::Sandboxable]
46 }
47
48 fn approval_requirement(&self) -> ApprovalRequirement {
49 ApprovalRequirement::Auto
50 }
51
52 async fn execute(&self, input: Value, context: &ToolContext) -> Result<ToolResult, ToolError> {
53 let max_depth = optional_u64(&input, "max_depth", 3)? as usize;
54 let map = generate_project_map(&context.workspace, max_depth, context.follow_symlinks)?;
55 ToolResult::json(&map).map_err(|e| ToolError::execution_failed(e.to_string()))
56 }
57 }
58
59 fn generate_project_map(
60 root: &std::path::Path,
61 max_depth: usize,
62 follow_symlinks: bool,
63 ) -> Result<ProjectMap, ToolError> {
64 let tree = project_tree(root, max_depth, follow_symlinks);
65 let summary = summarize_project(root);
66
67 // For key_files, we can just do a quick scan since summarize_project doesn't return them directly anymore
68 let mut key_files = Vec::new();
69 let mut builder = ignore::WalkBuilder::new(root);
70 builder
71 .hidden(false)
72 .follow_links(follow_symlinks)
73 .max_depth(Some(2));
74 let walker = builder.build();
75
76 for entry in walker.flatten() {
77 if entry.file_type().is_some_and(|ft| ft.is_symlink()) && !follow_symlinks {
78 continue;
79 }
80 if is_key_file(entry.path())
81 && let Ok(rel) = entry.path().strip_prefix(root)
82 {
83 key_files.push(rel.to_string_lossy().to_string());
84 }
85 }
86
87 Ok(ProjectMap {
88 tree,
89 summary,
90 key_files,
91 })
92 }
93
93 lines RUST