//! Workspace directory-tree generation for subagent system prompts. //! //! Build an ASCII tree of the workspace directory structure so the LLM //! can see the file layout — this is the same tree shown to the main //! agent and gives subagents the same project-awareness. //! //! Flow: for each workspace root, walk using `ignore::WalkBuilder` //! (respecting `.gitignore` and hidden files) → prefix `[DIR]` for //! directories → truncate after 1000 entries to keep the prompt //! reasonably sized. use std::fmt::Write; use tracing; /// Build an ASCII tree of the workspace directory structure for the /// system prompt, so the LLM can see the file layout. /// /// Flow: for each root, walk using `ignore::WalkBuilder` (respecting /// `.gitignore` and hidden files) → prefix `[DIR]` for directories → /// truncate after 1000 entries to keep the system prompt under control. /// /// The tree is appended to the subagent's system prompt so the LLM can /// reference file paths without having seen them in conversation. /// /// Return: a multi-line string containing the ASCII tree, or an empty /// string preamble + entries if no roots are provided. pub(crate) fn generate_workspace_tree(roots: &[std::path::PathBuf]) -> String { tracing::debug!("[subagent] generating workspace tree for {} root(s)", roots.len()); let mut out = String::new(); out.push_str("Current Workspace Directory Structure:\n"); for root in roots { // Print the root path as a section header. writeln!(out, "Root: {}", root.display()).unwrap(); // Walk the directory tree using ignore::WalkBuilder, which respects // .gitignore rules and hidden files by default. let walker = ignore::WalkBuilder::new(root) .hidden(true) .git_ignore(true) .build(); let mut count = 0; for entry in walker.flatten() { let path = entry.path(); if let Ok(rel) = path.strip_prefix(root) { // Skip the root entry itself (empty relative path). if rel.as_os_str().is_empty() { continue; } // Prefix directories with [DIR] for visual clarity. let is_dir = entry.file_type().is_some_and(|ft| ft.is_dir()); let prefix = if is_dir { "[DIR] " } else { " " }; writeln!(out, " {}{}", prefix, rel.display()).unwrap(); count += 1; // Hard cap at 1000 entries to avoid blowing up the prompt. if count > 1000 { tracing::info!("[subagent] workspace tree truncated at 1000 entries for '{}'", root.display()); out.push_str(" ... (truncated)\n"); break; } } } } tracing::debug!("[subagent] workspace tree generated ({} entries across {} roots)", out.lines().count(), roots.len()); out }