From bed9f8cff60711c4db998c8f10f32f8e210ae410 Mon Sep 17 00:00:00 2001 From: asepharyana Date: Mon, 20 Jul 2026 16:07:10 +0700 Subject: [PATCH] feat(tui): enhance system prompt with workspace structure information --- apps/infrastructure/src/utils.rs | 42 ++++++++++++++++++++++++++++++++ apps/interfaces/tui/src/turn.rs | 16 ++++++++---- 2 files changed, 53 insertions(+), 5 deletions(-) diff --git a/apps/infrastructure/src/utils.rs b/apps/infrastructure/src/utils.rs index b303f09..e3922ae 100644 --- a/apps/infrastructure/src/utils.rs +++ b/apps/infrastructure/src/utils.rs @@ -149,3 +149,45 @@ pub fn write_osc52(output: &mut impl Write, text: &str) -> std::io::Result<()> { output.flush()?; Ok(()) } + +// --------------------------------------------------------------------------- +// Workspace Tree Builder +// --------------------------------------------------------------------------- + +/// Build a string representing the directory tree of `root`, up to `max_files` limits. +/// Automatically respects `.gitignore` and `.ignore` via the `ignore` crate. +pub fn build_workspace_tree(root: &Path, max_files: usize) -> String { + use ignore::WalkBuilder; + let mut tree = String::new(); + let mut count = 0; + + // hidden(false) allows files like .github to be seen, but it still respects .gitignore + // and ignores .git directories by default. + for result in WalkBuilder::new(root).hidden(false).build() { + if let Ok(entry) = result { + if count >= max_files { + tree.push_str("\n... (truncated)"); + break; + } + + let path = entry.path(); + if let Ok(rel) = path.strip_prefix(root) { + let name = rel.to_string_lossy(); + if name.is_empty() { + tree.push_str(".\n"); + } else { + let depth = entry.depth(); + let indent = " ".repeat(depth.saturating_sub(1)); + let is_dir = entry.file_type().map(|ft| ft.is_dir()).unwrap_or(false); + let suffix = if is_dir { "/" } else { "" }; + + let filename = entry.file_name().to_string_lossy(); + tree.push_str(&format!("{indent}{filename}{suffix}\n")); + count += 1; + } + } + } + } + + tree.trim_end().to_string() +} diff --git a/apps/interfaces/tui/src/turn.rs b/apps/interfaces/tui/src/turn.rs index 9daf3c9..a046af8 100644 --- a/apps/interfaces/tui/src/turn.rs +++ b/apps/interfaces/tui/src/turn.rs @@ -120,15 +120,21 @@ fn run_turn(params: TurnParams) { let tools = all_tools(); let defs = tool_defs(&tools); - let sys_msg = ChatMessage::system( - "You are Zesdex, an AI coding assistant. You have access to various tools via native function calling to help the user. \ + let mut sys_prompt = "You are Zesdex, an AI coding assistant. You have access to various tools via native function calling to help the user. \ For complex problems, use `seq_think` to reason step-by-step. \ For any non-trivial tasks, you MUST prioritize creating a structured plan (using `plan_enter`) and a list of TODOs (using `todowrite`) BEFORE executing any other tools or modifying files. \ For large multi-step operations or delegating tasks, you MUST prioritize using `workflow_run` (to run a yaml workflow script) or `hive_mind` (to orchestrate multiple agents) to complete the task efficiently. \ Use other tools when necessary. If a tool returns an error, fix the issue before retrying. \ - Respond conversationally, concisely, and helpfully." - .to_string(), - ); + Respond conversationally, concisely, and helpfully.".to_string(); + + if let Some(root) = params.workspace_roots.first() { + let tree = zesdex_infrastructure::utils::build_workspace_tree(root, 800); + sys_prompt.push_str("\n\nWorkspace structure:\n```\n"); + sys_prompt.push_str(&tree); + sys_prompt.push_str("\n```\n"); + } + + let sys_msg = ChatMessage::system(sys_prompt); params.messages.insert(0, sys_msg); let tool_ctx = ToolCtx::builder()