1use serde_json::Value;
2
3pub async fn describe_toolchain(args: &Value) -> Result<String, String> {
4 let topic = args.get("topic").and_then(|v| v.as_str()).unwrap_or("all");
5 let question =
6 normalize_question_label(args.get("question").and_then(|v| v.as_str()).unwrap_or(""));
7
8 match topic {
9 "read_only_codebase" => Ok(describe_read_only_codebase_tools()),
10 "user_turn_plan" => Ok(describe_user_turn_plan(question)),
11 "voice_latency_plan" => Ok(describe_voice_latency_plan(question)),
12 "host_inspection_plan" => Ok(describe_host_inspection_plan(question)),
13 "all" => Ok(format!(
14 "{}\n\n{}",
15 describe_read_only_codebase_tools(),
16 describe_best_plan_for_question(question)
17 )),
18 other => Err(format!(
19 "Unknown topic '{}'. Use one of: read_only_codebase, user_turn_plan, voice_latency_plan, host_inspection_plan, all.",
20 other
21 )),
22 }
23}
24
25fn describe_best_plan_for_question(question: &str) -> String {
26 if is_voice_latency_question(question) {
27 describe_voice_latency_plan(question)
28 } else if is_host_inspection_question(question) {
29 describe_host_inspection_plan(question)
30 } else {
31 describe_user_turn_plan(question)
32 }
33}
34
35fn is_voice_latency_question(question: &str) -> bool {
36 let lower = question.to_lowercase();
37 (lower.contains("voice output") || lower.contains("voice"))
38 && (lower.contains("lag")
39 || lower.contains("behind visible text")
40 || lower.contains("latency"))
41}
42
43fn is_host_inspection_question(question: &str) -> bool {
44 let lower = question.to_lowercase();
45 let host_terms = [
46 "path",
47 "package manager",
48 "package managers",
49 "environment",
50 "env doctor",
51 "network",
52 "adapter",
53 "dns",
54 "gateway",
55 "ip address",
56 "service",
57 "services",
58 "daemon",
59 "startup type",
60 "desktop",
61 "downloads",
62 "toolchain",
63 "installed",
64 "version",
65 "directory",
66 "folder",
67 "computer",
68 "machine",
69 "port",
70 "process",
71 "environment",
72 ];
73 host_terms.iter().any(|needle| lower.contains(needle))
74}
75
76fn normalize_question_label(question: &str) -> &str {
77 let trimmed = question.trim();
78 if trimmed.is_empty() {
79 return trimmed;
80 }
81
82 if let Some(idx) = trimmed.find("Question:") {
83 let after = trimmed[idx + "Question:".len()..].trim();
84 if !after.is_empty() {
85 let requirement_markers = [
86 "Requirements:",
87 "Requirement:",
88 "Initial Investigation Order",
89 ];
90 let mut end = after.len();
91 for marker in requirement_markers {
92 if let Some(marker_idx) = after.find(marker) {
93 end = end.min(marker_idx);
94 }
95 }
96 return after[..end].trim();
97 }
98 }
99
100 trimmed
101}
102
103fn describe_read_only_codebase_tools() -> String {
104 "Verified Hematite read-only toolchain\n\n\
105Text search and file inspection\n\
106- `list_files`\n\
107 Good for: enumerating files in a directory, optionally narrowed by extension.\n\
108 Bad for: content search or semantic understanding.\n\
109 Choose it over another tool when: you know the directory area but need concrete file candidates.\n\
110- `grep_files`\n\
111 Good for: fast textual search across many files, including regex and context lines.\n\
112 Bad for: exact symbol definitions, types, or call relationships.\n\
113 Choose it over another tool when: you know a string pattern but not the owning symbol.\n\
114- `read_file`\n\
115 Good for: reading a full file or a large chunk once you know the target path.\n\
116 Bad for: precise line-range inspection in very large files.\n\
117 Choose it over another tool when: you already know the file and need broad local context.\n\
118- `inspect_lines`\n\
119 Good for: tight, line-ranged inspection after you know the relevant window.\n\
120 Bad for: first-pass exploration or cross-file search.\n\
121 Choose it over another tool when: you want exact nearby lines without rereading the whole file.\n\n\
122Semantic and LSP tools\n\
123- `lsp_search_symbol`\n\
124 Good for: jumping to a named symbol quickly across the workspace.\n\
125 Bad for: fuzzy textual patterns or unknown names.\n\
126 Choose it over another tool when: you know the symbol name and want the fastest semantic entry point.\n\
127- `lsp_definitions`\n\
128 Good for: confirming the exact definition site of a symbol at a position.\n\
129 Bad for: finding every caller or usage.\n\
130 Choose it over another tool when: you already have a coordinate and need the true definition.\n\
131- `lsp_references`\n\
132 Good for: tracing who uses a symbol across the project.\n\
133 Bad for: initial discovery when you do not know the symbol yet.\n\
134 Choose it over another tool when: you need impact analysis or call-flow expansion.\n\
135- `lsp_hover`\n\
136 Good for: quick type and documentation context at a position.\n\
137 Bad for: ownership mapping or full call graphs.\n\
138 Choose it over another tool when: you need a compact semantic summary before deeper reading.\n\
139- `lsp_get_diagnostics`\n\
140 Good for: current compiler and analysis errors on a file.\n\
141 Bad for: architecture understanding.\n\
142 Choose it over another tool when: you need to validate file health or check active breakage.\n\
143 Conditional: usefulness depends on the language server being available and healthy.\n\n\
144Runtime and control-flow tools\n\
145- `trace_runtime_flow`\n\
146 Good for: authoritative runtime/control-flow questions such as user turns, startup, session reset, and reasoning separation.\n\
147 Bad for: arbitrary feature ownership outside the built-in runtime reports.\n\
148 Choose it over another tool when: the user asks how data or events move through Hematite.\n\n\
149Web research and docs\n\
150- `research_web`\n\
151 Good for: external technical search when repo context is not enough.\n\
152 Bad for: internal code truth.\n\
153 Choose it over another tool when: you need current docs, standards, or API changes outside the repo.\n\
154 Conditional: only relevant when external information is needed.\n\
155- `fetch_docs`\n\
156 Good for: reading a specific documentation URL found elsewhere.\n\
157 Bad for: discovery.\n\
158 Choose it over another tool when: you already have the URL and want readable docs.\n\
159 Conditional: usually paired with `research_web`.\n\n\
160Vision\n\
161- `vision_analyze`\n\
162 Good for: screenshots, diagrams, and visual state confirmation.\n\
163 Bad for: source-of-truth code tracing.\n\
164 Choose it over another tool when: the input is visual rather than textual.\n\
165 Conditional: only relevant when an image is available and the vision path is enabled.\n\n\
166Shell and context management\n\
167- `inspect_host`\n\
168 Good for: structured read-only inspection of the current machine such as common developer tool versions, PATH analysis, environment/package-manager health, grounded fix plans for common workstation failures, network snapshots, service snapshots, process snapshots, desktop items, Downloads summaries, listening ports, repo-doctor checks, and arbitrary directory or disk-size reports.\n\
169 Bad for: custom build commands, arbitrary process control, or any mutation.\n\
170 Choose it over another tool when: the user is asking about the host machine rather than repo internals and the question fits one of its built-in topics.\n\
171- `run_hematite_maintainer_workflow`\n\
172 Good for: approval-gated execution of Hematite's own maintainer workflows such as cleanup, local Windows packaging, and the scripted release flow.\n\
173 Bad for: arbitrary shell commands, custom one-off scripts, or generic current-workspace script execution.\n\
174 Choose it over another tool when: the user explicitly asks to run Hematite's own `clean.ps1`, `scripts/package-windows.ps1`, `release.ps1`, or the natural-language equivalent of those workflows.\n\
175- `run_workspace_workflow`\n\
176 Good for: approval-gated execution of the active project's own build, test, lint, fix, package scripts, just/task/make targets, local scripts, or an exact workspace command.\n\
177 Bad for: Hematite's own maintainer workflows, host-only inspection, or commands that are not tied to the locked workspace root.\n\
178 Choose it over another tool when: the user wants something run in the current project and the command should be rooted to the locked workspace instead of the terminal launch directory.\n\
179- `shell`\n\
180 Good for: builds, tests, environment checks, and OS-level read-only inspection.\n\
181 Bad for: precise code understanding when built-in file and LSP tools are available.\n\
182 Choose it over another tool when: you need runtime verification, a custom command, or host information that `inspect_host`, `run_hematite_maintainer_workflow`, or `run_workspace_workflow` do not cover.\n\
183- `auto_pin_context`\n\
184 Good for: keeping 1-3 critical files in active memory during a complex investigation.\n\
185 Bad for: discovery by itself.\n\
186 Choose it over another tool when: the task spans several important files and you need them held stable.\n\
187- `list_pinned`\n\
188 Good for: confirming what is pinned right now.\n\
189 Bad for: learning anything new about the codebase.\n\
190 Choose it over another tool when: you want to inspect or audit the current pinned set.\n\n\
191Optional external surface\n\
192- `mcp__*` tools\n\
193 Good for: optional external capabilities from configured MCP servers.\n\
194 Bad for: baseline assumptions about Hematite's built-in tool surface.\n\
195 Choose them over another tool when: a configured MCP server is active and directly relevant.\n\
196 Conditional: they only exist when MCP servers are configured and loaded.\n\n\
197Best Read-Only Toolchain\n\
198- Start with `trace_runtime_flow` for runtime wiring questions.\n\
199- Use `grep_files` for textual discovery, then switch to `read_file` or `inspect_lines` for exact local context.\n\
200- Use `lsp_search_symbol`, `lsp_definitions`, `lsp_references`, and `lsp_hover` for semantic confirmation once you know the area.\n\
201- Use `inspect_host` before `shell` for read-only questions about PATH, installed tools, environment/package-manager health, grounded fix plans for common workstation failures, network state, service state, running processes, desktop items, Downloads size, listening ports, repo-health summaries, or directory/disk summaries.\n\
202- If the user asks how to fix a common workstation problem such as `cargo not found`, `port 3000 already in use`, or `LM Studio not reachable`, use `fix_plan` first instead of `env_doctor`, `path`, or `ports`.\n\
203- Use `run_hematite_maintainer_workflow` before raw `shell` when the user explicitly asks to run Hematite's own maintainer workflows like cleanup, local packaging, or the scripted release flow.\n\
204- Use `run_workspace_workflow` before raw `shell` when the user explicitly asks to run the current project's build, tests, package scripts, make/just/task targets, or local repo scripts.\n\
205- If `env_doctor` answers a PATH/package-manager sanity question, stop there unless the user explicitly asks for the raw PATH list.\n\
206- Use `shell` only when the answer requires runtime verification or host-state information beyond `inspect_host`.\n\
207- Use `research_web`, `fetch_docs`, and `vision_analyze` only when the question truly depends on external docs or images."
208 .to_string()
209}
210
211fn describe_user_turn_plan(question: &str) -> String {
212 let label = if question.trim().is_empty() {
213 "How does Hematite move a user message from the TUI to the model and back?"
214 } else {
215 question
216 };
217
218 format!(
219 "Concrete read-only investigation plan for: {:?}\n\n\
2201. `trace_runtime_flow`\n\
221 Why first: it is the most authoritative built-in tool for runtime/control-flow questions and already knows the exact Hematite event path categories such as `user_turn`.\n\
222 Use: request the `user_turn` report first so you get the verified top-level path before reading source.\n\
2232. `read_file`\n\
224 Why second: once the runtime trace identifies the owning files, read the specific owners directly instead of guessing from memory.\n\
225 Use: inspect `src/main.rs`, `src/ui/tui.rs`, `src/agent/conversation.rs`, and `src/agent/inference.rs` in broad chunks.\n\
2263. `inspect_lines`\n\
227 Why third: after the broad read, narrow to the exact line windows that contain `run_app`, `run_agent_task`, `ConversationManager::run_turn`, and the relevant `InferenceEvent` handling.\n\
228 Use: confirm the exact local flow without rereading unrelated code.\n\
2294. `lsp_search_symbol`\n\
230 Why fourth: if a specific symbol from the trace needs precise navigation, this is the fastest semantic jump.\n\
231 Use: search for symbols like `run_app`, `run_agent_task`, `ConversationManager::run_turn`, `InferenceEvent`, `extract_think_block`, or `strip_think_blocks` only after the trace names them.\n\
2325. `lsp_definitions`\n\
233 Why fifth: confirm the true definition site when a symbol appears in several places or when the file read is ambiguous.\n\
234 Use: anchor the investigation on the exact definition instead of a textual match.\n\
2356. `lsp_references`\n\
236 Why sixth: expand outward from a confirmed symbol to see who calls it and where the next handoff occurs.\n\
237 Use: trace the path from TUI submit code into the agent loop and then into inference handling.\n\
2387. `lsp_hover`\n\
239 Why seventh: fill semantic gaps quickly without extra reading when a type or event payload is unclear.\n\
240 Use: confirm what an enum variant or function signature carries at that point in the flow.\n\
2418. `auto_pin_context`\n\
242 Why eighth: once the 2-3 core files are obvious, pin them so a longer investigation does not drift.\n\
243 Use: pin the owner files after the first pass, not before.\n\
2449. `shell`\n\
245 Why last and only if needed: runtime verification belongs after source truth, not before it.\n\
246 Use: only when you need a build, a health check, or another host-level confirmation that the static code reading cannot provide.\n\n\
247Tools I would not start with\n\
248- `grep_files`: useful for fuzzy discovery, but weaker than `trace_runtime_flow` plus LSP once the target path is a known runtime flow.\n\
249- `research_web`, `fetch_docs`, `vision_analyze`: not first-choice tools for this repo-local runtime question.\n\
250\nBest Read-Only Toolchain\n\
251`trace_runtime_flow` -> `read_file` -> `inspect_lines` -> `lsp_search_symbol` -> `lsp_definitions` / `lsp_references` -> `lsp_hover` -> `auto_pin_context` -> optional `shell`",
252 label
253 )
254}
255
256fn describe_voice_latency_plan(question: &str) -> String {
257 let label = if question.trim().is_empty() {
258 "If I needed to understand why Hematite's voice output can lag behind visible text, what tools would I choose first, in order, and why?"
259 } else {
260 question
261 };
262
263 format!(
264 "Concrete read-only investigation plan for: {:?}\n\n\
2651. `trace_runtime_flow`\n\
266 Why first: it is the only authoritative built-in runtime/control-flow report, and it already covers the visible text path and the voice path inside a normal `user_turn` trace.\n\
267 Use: request the `user_turn` report first so you can see where visible `InferenceEvent::Token` handling and `app.voice_manager.speak(...)` diverge.\n\
2682. `read_file`\n\
269 Why second: once the high-level flow is confirmed, read the owner files directly instead of inventing helper layers.\n\
270 Use: inspect `src/ui/tui.rs` for `InferenceEvent::Token`, `InferenceEvent::MutedToken`, and `InferenceEvent::Done` handling, then inspect `src/ui/voice.rs` for `VoiceManager::new`, `VoiceManager::speak`, and `VoiceManager::flush`.\n\
2713. `inspect_lines`\n\
272 Why third: narrow to the exact windows where visible text is appended and where voice work is queued or flushed.\n\
273 Use: inspect the token-handling block in `src/ui/tui.rs` and the queueing / synthesis blocks in `src/ui/voice.rs` without rereading the full files.\n\
2744. `lsp_search_symbol`\n\
275 Why fourth: if you need precise navigation after the first file read, this is the fastest semantic jump.\n\
276 Use: search for `VoiceManager`, `VoiceManager::speak`, `VoiceManager::flush`, and `run_app`.\n\
2775. `lsp_references`\n\
278 Why fifth: confirm every place where the TUI calls into the voice path and where the relevant voice methods are used.\n\
279 Use: trace who calls `VoiceManager::speak` and `VoiceManager::flush` to see whether lag is created before queueing, during streaming, or at turn finalization.\n\
2806. `lsp_hover`\n\
281 Why sixth: quickly confirm type signatures and payload details for `InferenceEvent` handling and voice methods without extra full-file reading.\n\
282 Use: inspect the event variants and the `VoiceManager` method surfaces when the control-flow meaning is still unclear.\n\
2837. `lsp_definitions`\n\
284 Why seventh: anchor the final understanding on the true definition sites if a search result or reference set is ambiguous.\n\
285 Use: confirm exact definition coordinates for `VoiceManager` methods and the relevant `InferenceEvent` enum variants.\n\
2868. `shell`\n\
287 Why last and only if needed: shell is for runtime verification after the source investigation, not before it.\n\
288 Use: only if you need to confirm host-level load or reproduce the lag under observation after the static code path is understood.\n\n\
289Built-in authoritative tool note\n\
290- `trace_runtime_flow` is authoritative for part of this question because it already describes the visible chat path and the voice path inside a `user_turn` trace.\n\
291- It is not sufficient by itself to explain why lag happens inside `VoiceManager`, so the next step is direct file reading in `src/ui/tui.rs` and `src/ui/voice.rs`.\n\n\
292Tools I would not start with\n\
293- `mcp__*` tools: optional external surface, not the baseline for this built-in voice investigation.\n\
294- `research_web`, `fetch_docs`, `vision_analyze`: not first-choice tools for a repo-local voice-latency question.\n\
295\nInitial Investigation Order\n\
296`trace_runtime_flow` -> `read_file` -> `inspect_lines` -> `lsp_search_symbol` -> `lsp_references` -> `lsp_hover` -> `lsp_definitions` -> optional `shell`",
297 label
298 )
299}
300
301fn describe_host_inspection_plan(question: &str) -> String {
302 let label = if question.trim().is_empty() {
303 "What is the best read-only tool order for checking my machine state, installed tools, PATH, environment/package-manager health, network adapters, services, desktop items, or folder sizes?"
304 } else {
305 question
306 };
307
308 format!(
309 "Concrete read-only investigation plan for: {:?}\n\n\
3101. `inspect_host`\n\
311 Why first: it is the built-in structured host-inspection tool, so it can answer common machine-state questions without forcing the model to invent shell commands.\n\
312 Use: start with the closest topic such as `summary`, `toolchains`, `path`, `env_doctor`, `fix_plan`, `network`, `services`, `processes`, `desktop`, `downloads`, `ports`, `repo_doctor`, `directory`, or `disk`.\n\
3132. `shell`\n\
314 Why second and only if needed: shell is still the fallback for custom host checks that go beyond `inspect_host`, but it should not be the first move for routine read-only inspection.\n\
315 Use: confirm a special case, run a project-specific command, or inspect host state that has no structured built-in topic yet.\n\
3163. `read_file` / `list_files`\n\
317 Why third and conditional: if the question shifts from host state back into the workspace, move to file tools instead of staying in shell.\n\
318 Use: inspect repo files, logs, or config once the machine-level question identifies the relevant path.\n\n\
319Tools I would not start with\n\
320- `grep_files`: useful for repo text search, but not the right first tool for PATH or desktop questions.\n\
321- `trace_runtime_flow`: useful for Hematite runtime architecture, not machine-state inspection.\n\
322- `research_web`, `fetch_docs`, `vision_analyze`: only relevant if the question expands beyond the local machine.\n\n\
323Initial Investigation Order\n\
324`inspect_host` -> optional `shell` -> optional repo/file tools",
325 label
326 )
327}