Skip to main content

hematite/tools/
toolchain.rs

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        "desktop",
48        "downloads",
49        "toolchain",
50        "installed",
51        "version",
52        "directory",
53        "folder",
54        "computer",
55        "machine",
56        "port",
57        "process",
58        "environment",
59    ];
60    host_terms.iter().any(|needle| lower.contains(needle))
61}
62
63fn normalize_question_label(question: &str) -> &str {
64    let trimmed = question.trim();
65    if trimmed.is_empty() {
66        return trimmed;
67    }
68
69    if let Some(idx) = trimmed.find("Question:") {
70        let after = trimmed[idx + "Question:".len()..].trim();
71        if !after.is_empty() {
72            let requirement_markers = [
73                "Requirements:",
74                "Requirement:",
75                "Initial Investigation Order",
76            ];
77            let mut end = after.len();
78            for marker in requirement_markers {
79                if let Some(marker_idx) = after.find(marker) {
80                    end = end.min(marker_idx);
81                }
82            }
83            return after[..end].trim();
84        }
85    }
86
87    trimmed
88}
89
90fn describe_read_only_codebase_tools() -> String {
91    "Verified Hematite read-only toolchain\n\n\
92Text search and file inspection\n\
93- `map_project`\n\
94  Good for: first-pass spatial awareness of the repository layout, likely entrypoints, core owner files, and a small set of extracted top symbols.\n\
95  Bad for: exact control flow, full call graphs, or precise line-level inspection.\n\
96  Choose it over another tool when: you need a compact architecture map before diving into files or LSP.\n\
97- `list_files`\n\
98  Good for: enumerating files in a directory, optionally narrowed by extension.\n\
99  Bad for: content search or semantic understanding.\n\
100  Choose it over another tool when: you know the directory area but need concrete file candidates.\n\
101- `grep_files`\n\
102  Good for: fast textual search across many files, including regex and context lines.\n\
103  Bad for: exact symbol definitions, types, or call relationships.\n\
104  Choose it over another tool when: you know a string pattern but not the owning symbol.\n\
105- `read_file`\n\
106  Good for: reading a full file or a large chunk once you know the target path.\n\
107  Bad for: precise line-range inspection in very large files.\n\
108  Choose it over another tool when: you already know the file and need broad local context.\n\
109- `inspect_lines`\n\
110  Good for: tight, line-ranged inspection after you know the relevant window.\n\
111  Bad for: first-pass exploration or cross-file search.\n\
112  Choose it over another tool when: you want exact nearby lines without rereading the whole file.\n\n\
113Semantic and LSP tools\n\
114- `lsp_search_symbol`\n\
115  Good for: jumping to a named symbol quickly across the workspace.\n\
116  Bad for: fuzzy textual patterns or unknown names.\n\
117  Choose it over another tool when: you know the symbol name and want the fastest semantic entry point.\n\
118- `lsp_definitions`\n\
119  Good for: confirming the exact definition site of a symbol at a position.\n\
120  Bad for: finding every caller or usage.\n\
121  Choose it over another tool when: you already have a coordinate and need the true definition.\n\
122- `lsp_references`\n\
123  Good for: tracing who uses a symbol across the project.\n\
124  Bad for: initial discovery when you do not know the symbol yet.\n\
125  Choose it over another tool when: you need impact analysis or call-flow expansion.\n\
126- `lsp_hover`\n\
127  Good for: quick type and documentation context at a position.\n\
128  Bad for: ownership mapping or full call graphs.\n\
129  Choose it over another tool when: you need a compact semantic summary before deeper reading.\n\
130- `lsp_get_diagnostics`\n\
131  Good for: current compiler and analysis errors on a file.\n\
132  Bad for: architecture understanding.\n\
133  Choose it over another tool when: you need to validate file health or check active breakage.\n\
134  Conditional: usefulness depends on the language server being available and healthy.\n\n\
135Runtime and control-flow tools\n\
136- `trace_runtime_flow`\n\
137  Good for: authoritative runtime/control-flow questions such as user turns, startup, session reset, and reasoning separation.\n\
138  Bad for: arbitrary feature ownership outside the built-in runtime reports.\n\
139  Choose it over another tool when: the user asks how data or events move through Hematite.\n\n\
140Web research and docs\n\
141- `research_web`\n\
142  Good for: external technical search when repo context is not enough.\n\
143  Bad for: internal code truth.\n\
144  Choose it over another tool when: you need current docs, standards, or API changes outside the repo.\n\
145  Conditional: only relevant when external information is needed.\n\
146- `fetch_docs`\n\
147  Good for: reading a specific documentation URL found elsewhere.\n\
148  Bad for: discovery.\n\
149  Choose it over another tool when: you already have the URL and want readable docs.\n\
150  Conditional: usually paired with `research_web`.\n\n\
151Vision\n\
152- `vision_analyze`\n\
153  Good for: screenshots, diagrams, and visual state confirmation.\n\
154  Bad for: source-of-truth code tracing.\n\
155  Choose it over another tool when: the input is visual rather than textual.\n\
156  Conditional: only relevant when an image is available and the vision path is enabled.\n\n\
157Shell and context management\n\
158- `inspect_host`\n\
159  Good for: structured read-only inspection of the current machine such as common developer tool versions, PATH analysis, desktop items, Downloads summaries, listening ports, repo-doctor checks, and arbitrary directory or disk-size reports.\n\
160  Bad for: custom build commands, arbitrary process control, or any mutation.\n\
161  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\
162- `shell`\n\
163  Good for: builds, tests, environment checks, and OS-level read-only inspection.\n\
164  Bad for: precise code understanding when built-in file and LSP tools are available.\n\
165  Choose it over another tool when: you need runtime verification, a custom command, or host information that `inspect_host` cannot answer directly.\n\
166- `auto_pin_context`\n\
167  Good for: keeping 1-3 critical files in active memory during a complex investigation.\n\
168  Bad for: discovery by itself.\n\
169  Choose it over another tool when: the task spans several important files and you need them held stable.\n\
170- `list_pinned`\n\
171  Good for: confirming what is pinned right now.\n\
172  Bad for: learning anything new about the codebase.\n\
173  Choose it over another tool when: you want to inspect or audit the current pinned set.\n\n\
174Optional external surface\n\
175- `mcp__*` tools\n\
176  Good for: optional external capabilities from configured MCP servers.\n\
177  Bad for: baseline assumptions about Hematite's built-in tool surface.\n\
178  Choose them over another tool when: a configured MCP server is active and directly relevant.\n\
179  Conditional: they only exist when MCP servers are configured and loaded.\n\n\
180Best Read-Only Toolchain\n\
181- Start with `trace_runtime_flow` for runtime wiring questions.\n\
182- Use `map_project` only when ownership or structure is still unclear.\n\
183- Use `grep_files` for textual discovery, then switch to `read_file` or `inspect_lines` for exact local context.\n\
184- Use `lsp_search_symbol`, `lsp_definitions`, `lsp_references`, and `lsp_hover` for semantic confirmation once you know the area.\n\
185- Use `inspect_host` before `shell` for read-only questions about PATH, installed tools, desktop items, Downloads size, listening ports, repo-health summaries, or directory/disk summaries.\n\
186- Use `shell` only when the answer requires runtime verification or host-state information beyond `inspect_host`.\n\
187- Use `research_web`, `fetch_docs`, and `vision_analyze` only when the question truly depends on external docs or images."
188        .to_string()
189}
190
191fn describe_user_turn_plan(question: &str) -> String {
192    let label = if question.trim().is_empty() {
193        "How does Hematite move a user message from the TUI to the model and back?"
194    } else {
195        question
196    };
197
198    format!(
199        "Concrete read-only investigation plan for: {:?}\n\n\
2001. `trace_runtime_flow`\n\
201   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\
202   Use: request the `user_turn` report first so you get the verified top-level path before reading source.\n\
2032. `read_file`\n\
204   Why second: once the runtime trace identifies the owning files, read the specific owners directly instead of guessing from memory.\n\
205   Use: inspect `src/main.rs`, `src/ui/tui.rs`, `src/agent/conversation.rs`, and `src/agent/inference.rs` in broad chunks.\n\
2063. `inspect_lines`\n\
207   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\
208   Use: confirm the exact local flow without rereading unrelated code.\n\
2094. `lsp_search_symbol`\n\
210   Why fourth: if a specific symbol from the trace needs precise navigation, this is the fastest semantic jump.\n\
211   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\
2125. `lsp_definitions`\n\
213   Why fifth: confirm the true definition site when a symbol appears in several places or when the file read is ambiguous.\n\
214   Use: anchor the investigation on the exact definition instead of a textual match.\n\
2156. `lsp_references`\n\
216   Why sixth: expand outward from a confirmed symbol to see who calls it and where the next handoff occurs.\n\
217   Use: trace the path from TUI submit code into the agent loop and then into inference handling.\n\
2187. `lsp_hover`\n\
219   Why seventh: fill semantic gaps quickly without extra reading when a type or event payload is unclear.\n\
220   Use: confirm what an enum variant or function signature carries at that point in the flow.\n\
2218. `auto_pin_context`\n\
222   Why eighth: once the 2-3 core files are obvious, pin them so a longer investigation does not drift.\n\
223   Use: pin the owner files after the first pass, not before.\n\
2249. `shell`\n\
225   Why last and only if needed: runtime verification belongs after source truth, not before it.\n\
226   Use: only when you need a build, a health check, or another host-level confirmation that the static code reading cannot provide.\n\n\
227Tools I would not start with\n\
228- `map_project`: useful for initial orientation, but unnecessary if `trace_runtime_flow` already identifies the owner files.\n\
229- `grep_files`: useful for fuzzy discovery, but weaker than `trace_runtime_flow` plus LSP once the target path is a known runtime flow.\n\
230- `research_web`, `fetch_docs`, `vision_analyze`: not first-choice tools for this repo-local runtime question.\n\
231\nBest Read-Only Toolchain\n\
232`trace_runtime_flow` -> `read_file` -> `inspect_lines` -> `lsp_search_symbol` -> `lsp_definitions` / `lsp_references` -> `lsp_hover` -> `auto_pin_context` -> optional `shell`",
233        label
234    )
235}
236
237fn describe_voice_latency_plan(question: &str) -> String {
238    let label = if question.trim().is_empty() {
239        "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?"
240    } else {
241        question
242    };
243
244    format!(
245        "Concrete read-only investigation plan for: {:?}\n\n\
2461. `trace_runtime_flow`\n\
247   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\
248   Use: request the `user_turn` report first so you can see where visible `InferenceEvent::Token` handling and `app.voice_manager.speak(...)` diverge.\n\
2492. `read_file`\n\
250   Why second: once the high-level flow is confirmed, read the owner files directly instead of inventing helper layers.\n\
251   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\
2523. `inspect_lines`\n\
253   Why third: narrow to the exact windows where visible text is appended and where voice work is queued or flushed.\n\
254   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\
2554. `lsp_search_symbol`\n\
256   Why fourth: if you need precise navigation after the first file read, this is the fastest semantic jump.\n\
257   Use: search for `VoiceManager`, `VoiceManager::speak`, `VoiceManager::flush`, and `run_app`.\n\
2585. `lsp_references`\n\
259   Why fifth: confirm every place where the TUI calls into the voice path and where the relevant voice methods are used.\n\
260   Use: trace who calls `VoiceManager::speak` and `VoiceManager::flush` to see whether lag is created before queueing, during streaming, or at turn finalization.\n\
2616. `lsp_hover`\n\
262   Why sixth: quickly confirm type signatures and payload details for `InferenceEvent` handling and voice methods without extra full-file reading.\n\
263   Use: inspect the event variants and the `VoiceManager` method surfaces when the control-flow meaning is still unclear.\n\
2647. `lsp_definitions`\n\
265   Why seventh: anchor the final understanding on the true definition sites if a search result or reference set is ambiguous.\n\
266   Use: confirm exact definition coordinates for `VoiceManager` methods and the relevant `InferenceEvent` enum variants.\n\
2678. `shell`\n\
268   Why last and only if needed: shell is for runtime verification after the source investigation, not before it.\n\
269   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\
270Built-in authoritative tool note\n\
271- `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\
272- 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\
273Tools I would not start with\n\
274- `mcp__*` tools: optional external surface, not the baseline for this built-in voice investigation.\n\
275- `research_web`, `fetch_docs`, `vision_analyze`: not first-choice tools for a repo-local voice-latency question.\n\
276- `map_project`: useful if ownership were unclear, but unnecessary here because the runtime trace and symbol names already point to the likely owners.\n\
277\nInitial Investigation Order\n\
278`trace_runtime_flow` -> `read_file` -> `inspect_lines` -> `lsp_search_symbol` -> `lsp_references` -> `lsp_hover` -> `lsp_definitions` -> optional `shell`",
279        label
280    )
281}
282
283fn describe_host_inspection_plan(question: &str) -> String {
284    let label = if question.trim().is_empty() {
285        "What is the best read-only tool order for checking my machine state, installed tools, PATH, desktop items, or folder sizes?"
286    } else {
287        question
288    };
289
290    format!(
291        "Concrete read-only investigation plan for: {:?}\n\n\
2921. `inspect_host`\n\
293   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\
294   Use: start with the closest topic such as `summary`, `toolchains`, `path`, `desktop`, `downloads`, `ports`, `repo_doctor`, `directory`, or `disk`.\n\
2952. `shell`\n\
296   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\
297   Use: confirm a special case, run a project-specific command, or inspect host state that has no structured built-in topic yet.\n\
2983. `read_file` / `list_files`\n\
299   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\
300   Use: inspect repo files, logs, or config once the machine-level question identifies the relevant path.\n\n\
301Tools I would not start with\n\
302- `grep_files`: useful for repo text search, but not the right first tool for PATH or desktop questions.\n\
303- `trace_runtime_flow`: useful for Hematite runtime architecture, not machine-state inspection.\n\
304- `research_web`, `fetch_docs`, `vision_analyze`: only relevant if the question expands beyond the local machine.\n\n\
305Initial Investigation Order\n\
306`inspect_host` -> optional `shell` -> optional repo/file tools",
307        label
308    )
309}