Skip to main content

bamboo_tools/guide/
context.rs

1//! Context for building tool usage guides.
2//!
3//! This module provides language detection and context building for generating
4//! localized tool usage guidelines that match the language of the system prompt.
5
6const GIT_TOOL_GUIDANCE: &str = "For Git work, prefer available dedicated Git tools over reconstructing repository state by reading files one by one. Start with status and staged/unstaged diff; for branch comparison or conflicts, inspect the merge base, unique commits, and unmerged index before reading the necessary diff hunks and surrounding source to resolve intent. Batch independent read-only queries. Preserve dirty user work and unique commits; verify the resolved diff and relevant checks. Use exec_command only when a dedicated tool cannot provide the needed operation and your role permits shell execution. A Supervisor without shell access delegates Git execution to an authorized worker and reviews its evidence; missing tools never expand permissions.";
7
8/// Language options for guide generation.
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum GuideLanguage {
11    Chinese,
12    English,
13}
14
15impl GuideLanguage {
16    /// Detects the language from source text.
17    ///
18    /// Returns `Chinese` if CJK characters are detected, otherwise `English`.
19    pub fn detect(source: &str) -> Self {
20        if source.chars().any(is_cjk) {
21            Self::Chinese
22        } else {
23            Self::English
24        }
25    }
26}
27
28/// Build context for generating tool guides.
29///
30/// Contains language settings and configuration options for guide generation.
31#[derive(Debug, Clone)]
32pub struct GuideBuildContext {
33    /// Detected or configured language for guide content
34    pub language: GuideLanguage,
35    /// Whether to include best practices section
36    pub include_best_practices: bool,
37    /// Maximum number of examples to include per tool
38    pub max_examples_per_tool: usize,
39    /// Discoverable tools that are currently activated for the session.
40    /// Activated discoverable tools render with full guides; inactive ones
41    /// render with short summaries only.
42    pub activated_discoverable_tools: std::collections::BTreeSet<String>,
43}
44
45impl Default for GuideBuildContext {
46    fn default() -> Self {
47        Self {
48            language: GuideLanguage::English,
49            include_best_practices: true,
50            max_examples_per_tool: 2,
51            activated_discoverable_tools: std::collections::BTreeSet::new(),
52        }
53    }
54}
55
56impl GuideBuildContext {
57    /// Creates a build context by detecting language from a system prompt.
58    ///
59    /// # Arguments
60    ///
61    /// * `prompt` - The system prompt to analyze for language detection
62    pub fn from_system_prompt(prompt: &str) -> Self {
63        Self {
64            language: GuideLanguage::detect(prompt),
65            ..Self::default()
66        }
67    }
68
69    /// Returns best practices appropriate for the configured language.
70    pub fn best_practices(&self) -> &'static [&'static str] {
71        match self.language {
72            GuideLanguage::Chinese => &[
73                "Assist with defensive security tasks only; refuse offensive security requests, credential harvesting, and malware-oriented code changes.",
74                "For multi-step or non-trivial work, use Task to maintain a shared task list and keep statuses updated continuously.",
75                "Keep exactly one Task item in in_progress whenever possible; mark items completed immediately after finishing.",
76                "Read existing files before editing them. Use Edit for targeted replacements and Write only for full-file writes.",
77                "Use Glob for file discovery and Grep for content search. Do not use exec_command for find/grep/cat/head/tail/sed/awk/echo style file operations when dedicated tools exist.",
78                "For code search, narrow scope first: Glob -> Grep(files_with_matches) -> Read -> Grep(content/multiline).",
79                "Avoid broad Grep queries; keep head_limit small and add path/glob/type before multiline or content-heavy searches.",
80                "Use exec_command only for real terminal operations (build/test/git/npm/docker/etc.), not for file browsing or user-facing communication.",
81                "When multiple exec_command calls are independent, run them in parallel. For dependent commands, chain with &&.",
82                "When exec_command returns a running session_id, use write_stdin with empty chars to pull incremental output and final exit status. Choose yield_time_ms to bound each wait; completion does not send an automatic user notification. Unread completed results expire five minutes after completion, including during a diagnostic pause; handles do not survive Host restart. Poll required commands to a terminal result before finishing the task.",
83                "When polling reports sustained silence and repeated empty readouts, follow its advisory: do useful work or check one command-owned progress signal before polling again. Quiet output or an existing report does not prove completion; keep the handle, continue waiting when appropriate, and report a concrete blocker if progress cannot be established. A Human-controlled Root can pause polling after the reminder is repeatedly ignored, retaining the command handle for continuation. Do not stop/restart a command only because it is quiet.",
84                "**Parallel tool calls are strongly preferred.** The following read-only tools can safely run in parallel and SHOULD be called together in the same response whenever possible: FileExists, Glob, GetCurrentDir, GetFileInfo, Grep, Read, ViewImage, WebFetch, session_history_current, session_history. For example, if you need to read 3 files, emit all 3 Read calls in one response instead of one at a time.",
85                GIT_TOOL_GUIDANCE,
86                "For commit requests: inspect git status, git diff, and recent git log first; commit within current or standing user authorization; avoid interactive git flags; use HEREDOC for multi-line commit messages.",
87                "For pull request requests: review all commits/diff since base branch, use gh to create PR with summary and test plan, and return the PR URL.",
88                "When referencing code locations in responses, use file_path:line_number format.",
89                "Use Plan to delegate planning to its read-only child and keep the root session in normal orchestration mode. Use ExitPlanMode only to leave an already-active legacy plan-mode session.",
90            ],
91            GuideLanguage::English => &[
92                "Assist with defensive security tasks only; refuse offensive security requests, credential harvesting, and malware-oriented code changes.",
93                "For multi-step or non-trivial work, use Task to maintain a shared task list and keep statuses updated continuously.",
94                "Keep exactly one Task item in in_progress whenever possible; mark items completed immediately after finishing.",
95                "Read existing files before editing them. Use Edit for targeted replacements and Write only for full-file writes.",
96                "Use Glob for file discovery and Grep for content search. Do not use exec_command for find/grep/cat/head/tail/sed/awk/echo style file operations when dedicated tools exist.",
97                "For code search, narrow scope first: Glob -> Grep(files_with_matches) -> Read -> Grep(content/multiline).",
98                "Avoid broad Grep queries; keep head_limit small and add path/glob/type before multiline or content-heavy searches.",
99                "Use exec_command only for real terminal operations (build/test/git/npm/docker/etc.), not for file browsing or user-facing communication.",
100                "When multiple exec_command calls are independent, run them in parallel. For dependent commands, chain with &&.",
101                "When exec_command returns a running session_id, use write_stdin with empty chars to pull incremental output and final exit status. Choose yield_time_ms to bound each wait; completion does not send an automatic user notification. Unread completed results expire five minutes after completion, including during a diagnostic pause; handles do not survive Host restart. Poll required commands to a terminal result before finishing the task.",
102                "When polling reports sustained silence and repeated empty readouts, follow its advisory: do useful work or check one command-owned progress signal before polling again. Quiet output or an existing report does not prove completion; keep the handle, continue waiting when appropriate, and report a concrete blocker if progress cannot be established. A Human-controlled Root can pause polling after the reminder is repeatedly ignored, retaining the command handle for continuation. Do not stop/restart a command only because it is quiet.",
103                "**Parallel tool calls are strongly preferred.** The following read-only tools can safely run in parallel and SHOULD be called together in the same response whenever possible: FileExists, Glob, GetCurrentDir, GetFileInfo, Grep, Read, ViewImage, WebFetch, session_history_current, session_history. For example, if you need to read 3 files, emit all 3 Read calls in one response instead of one at a time.",
104                GIT_TOOL_GUIDANCE,
105                "For commit requests: inspect git status, git diff, and recent git log first; commit within current or standing user authorization; avoid interactive git flags; use HEREDOC for multi-line commit messages.",
106                "For pull request requests: review all commits/diff since base branch, use gh to create PR with summary and test plan, and return the PR URL.",
107                "When referencing code locations in responses, use file_path:line_number format.",
108                "Use Plan to delegate planning to its read-only child and keep the root session in normal orchestration mode. Use ExitPlanMode only to leave an already-active legacy plan-mode session.",
109            ],
110        }
111    }
112}
113
114fn is_cjk(ch: char) -> bool {
115    matches!(
116        ch,
117        '\u{3400}'..='\u{4DBF}' | '\u{4E00}'..='\u{9FFF}' | '\u{F900}'..='\u{FAFF}'
118    )
119}
120
121#[cfg(test)]
122mod tests {
123    use super::{GuideBuildContext, GuideLanguage};
124
125    #[test]
126    fn detect_language_prefers_chinese_when_cjk_present() {
127        assert_eq!(
128            GuideLanguage::detect("Please help me modify this file"),
129            GuideLanguage::English
130        );
131    }
132
133    #[test]
134    fn best_practices_prefer_delegated_plan_without_legacy_mode_switch() {
135        for language in [GuideLanguage::Chinese, GuideLanguage::English] {
136            let context = GuideBuildContext {
137                language,
138                ..GuideBuildContext::default()
139            };
140            let guidance = context.best_practices().join("\n");
141            assert!(guidance.contains("delegate planning to its read-only child"));
142            assert!(guidance.contains("already-active legacy plan-mode session"));
143        }
144    }
145
146    #[test]
147    fn best_practices_teach_model_owned_exec_polling() {
148        for language in [GuideLanguage::Chinese, GuideLanguage::English] {
149            let context = GuideBuildContext {
150                language,
151                ..GuideBuildContext::default()
152            };
153            let guidance = context.best_practices().join("\n");
154            assert!(guidance.contains("exec_command"));
155            assert!(guidance.contains("write_stdin with empty chars"));
156            assert!(guidance.contains("does not send an automatic user notification"));
157            assert!(guidance.contains("follow its advisory"));
158            assert!(guidance.contains("does not prove completion"));
159            assert!(!guidance.contains("Bash"));
160        }
161    }
162
163    #[test]
164    fn best_practices_prefer_git_tools_and_preserve_role_and_work_boundaries() {
165        for language in [GuideLanguage::Chinese, GuideLanguage::English] {
166            let guidance = GuideBuildContext {
167                language,
168                ..GuideBuildContext::default()
169            }
170            .best_practices()
171            .join("\n");
172            for required in [
173                "prefer available dedicated Git tools",
174                "status and staged/unstaged diff",
175                "merge base, unique commits, and unmerged index",
176                "necessary diff hunks and surrounding source",
177                "Preserve dirty user work and unique commits",
178                "Supervisor without shell access delegates Git execution to an authorized worker",
179                "missing tools never expand permissions",
180                "current or standing user authorization",
181            ] {
182                assert!(guidance.contains(required), "missing {required}");
183            }
184        }
185    }
186
187    #[test]
188    fn detect_language_defaults_to_english_without_cjk() {
189        assert_eq!(
190            GuideLanguage::detect("Please inspect the codebase"),
191            GuideLanguage::English
192        );
193    }
194
195    #[test]
196    fn from_system_prompt_carries_detected_language() {
197        let context = GuideBuildContext::from_system_prompt("You are a coding assistant.");
198        assert_eq!(context.language, GuideLanguage::English);
199    }
200}