1use crate::agent::inference::{tool_metadata_for_name, ToolDefinition, ToolFunction};
2use serde_json::Value;
3
4fn make_tool(name: &str, description: &str, parameters: Value) -> ToolDefinition {
5 ToolDefinition {
6 tool_type: "function".into(),
7 function: ToolFunction {
8 name: name.into(),
9 description: description.into(),
10 parameters,
11 },
12 metadata: tool_metadata_for_name(name),
13 }
14}
15
16pub fn get_tools() -> Vec<ToolDefinition> {
18 let os = std::env::consts::OS;
19 let mut tools = vec![
20 make_tool(
21 "shell",
22 &format!(
23 "Execute a command in the host shell ({os}). \
24 Use this for building, testing, or system operations. \
25 Output is capped at 64KB. Prefer non-interactive commands."
26 ),
27 serde_json::json!({
28 "type": "object",
29 "properties": {
30 "command": {
31 "type": "string",
32 "description": "The command to run"
33 },
34 "reason": {
35 "type": "string",
36 "description": "For risky shell calls, explain what this command is verifying or changing."
37 },
38 "timeout_secs": {
39 "type": "integer",
40 "description": "Optional timeout in seconds (default 60)"
41 }
42 },
43 "required": ["command"]
44 }),
45 ),
46 make_tool(
47 "run_code",
48 "Execute a short JavaScript/TypeScript or Python snippet in a sandboxed subprocess. \
49 No network access, no filesystem escape, hard 10-second timeout. \
50 Use this to verify logic, test algorithms, compute values, or test functions \
51 when you need real output rather than a guess. \
52 ALWAYS include the `language` field — there is no default. \
53 \
54 JAVASCRIPT/TYPESCRIPT (language: \"javascript\"): \
55 Runs via Deno, NOT Node.js. `require()` does not exist — never use it. \
56 URL imports (e.g. from 'https://deno.land/...') are blocked — network is off. \
57 Use built-in Web APIs only: `crypto.subtle`, `TextEncoder`, `URL`, `atob`/`btoa`, etc. \
58 SHA-256 example: \
59 const buf = await crypto.subtle.digest('SHA-256', new TextEncoder().encode('hello')); \
60 console.log([...new Uint8Array(buf)].map(b=>b.toString(16).padStart(2,'0')).join('')); \
61 \
62 PYTHON (language: \"python\"): \
63 Standard library is available. `hashlib`, `json`, `math`, `datetime`, `re`, `itertools` all work. \
64 `subprocess`, `socket`, `urllib`, `requests` are blocked. \
65 SHA-256 example: import hashlib; print(hashlib.sha256(b'hello').hexdigest()) \
66 \
67 Do NOT fall back to shell to run deno, python, or node — use this tool directly.",
68 serde_json::json!({
69 "type": "object",
70 "properties": {
71 "language": {
72 "type": "string",
73 "enum": ["javascript", "typescript", "python"],
74 "description": "The language to run. javascript/typescript requires Deno; python requires Python 3."
75 },
76 "code": {
77 "type": "string",
78 "description": "The code to execute. Keep it short and self-contained. Print results to stdout."
79 },
80 "timeout_seconds": {
81 "type": "integer",
82 "description": "Max execution time in seconds (default 10, max 60). Use higher values for longer computations."
83 }
84 },
85 "required": ["language", "code"]
86 }),
87 ),
88
89 make_tool(
90 "trace_runtime_flow",
91 "Return an authoritative read-only trace of Hematite runtime flow. \
92 Use this for architecture questions about keyboard input to final output, \
93 reasoning/specular separation, startup wiring, runtime subsystems, \
94 voice synthesis and Ctrl+T toggle, or \
95 session reset commands like /clear, /new, and /forget. Prefer this over guessing.",
96 serde_json::json!({
97 "type": "object",
98 "properties": {
99 "topic": {
100 "type": "string",
101 "enum": ["user_turn", "session_reset", "reasoning_split", "runtime_subsystems", "startup", "voice"],
102 "description": "Which verified runtime report to return. Use 'voice' for any question about Ctrl+T, voice toggle, or TTS pipeline. Use 'user_turn' for keyboard-to-output flow. Use 'session_reset' for /clear, /forget, /new. Use 'startup' for startup wiring. Use 'reasoning_split' for specular/thought routing. Use 'runtime_subsystems' for background subsystem overview."
103 },
104 "input": {
105 "type": "string",
106 "description": "Optional user input to label a normal user-turn trace"
107 },
108 "command": {
109 "type": "string",
110 "enum": ["/clear", "/new", "/forget", "all"],
111 "description": "Optional reset command when topic=session_reset"
112 }
113 },
114 "required": ["topic"]
115 }),
116 ),
117 make_tool(
118 "describe_toolchain",
119 "Return an authoritative read-only description of Hematite's actual tool surface and investigation strategy. \
120 Use this for tooling-discipline questions, best-tool selection, or read-only plans for tracing runtime behavior. \
121 Prefer this over improvising tool names or investigation steps from memory.",
122 serde_json::json!({
123 "type": "object",
124 "properties": {
125 "topic": {
126 "type": "string",
127 "enum": ["read_only_codebase", "user_turn_plan", "voice_latency_plan", "host_inspection_plan", "all"],
128 "description": "Which authoritative toolchain report to return"
129 },
130 "question": {
131 "type": "string",
132 "description": "Optional user question to label or tailor the read-only investigation plan"
133 }
134 }
135 }),
136 ),
137 make_tool(
138 "inspect_host",
139 "Return a structured read-only inspection of the current machine and environment. \
140 Prefer this over raw shell for questions about OS configuration (firewall, power, uptime), plain-English system health reports, installed developer tools, PATH issues, package-manager and environment health, network state, service state, running processes, desktop items, Downloads size, listening ports, repo health, or directory/disk summaries. \
141 For high-performance hardware testing, use topic=disk_benchmark to measure real-time kernel disk queue intensity. \
142 For remediation questions phrased like 'how do I fix cargo not found', 'how do I fix port 3000 already in use', or 'how do I fix LM Studio not reachable', use topic=fix_plan instead of diagnosis-only topics like env_doctor, path, or ports. \
143 Use topic=summary for a compact host snapshot, topic=toolchains for common dev tool versions, topic=path for PATH analysis, topic=env_doctor for package-manager and PATH health, topic=fix_plan for structured remediation plans, topic=network for adapters/IPs/gateways/DNS, topic=services for service status and startup mode, \
144 topic=processes for top processes by memory/cpu and real-time disk/network I/O stats (look for [I/O R:N/W:N] tags to identify disk-heavy processes), \
145 topic=desktop or topic=downloads for known folders, topic=ports for listening endpoints, topic=repo_doctor for a structured workspace health report, \
146 topic=log_check for recent critical/error events from system event logs or journalctl, topic=startup_items for programs and services that run at boot (registry Run keys and startup folders on Windows; systemd enabled units on Linux), \
147 topic=health_report for a plain-English tiered system health verdict (disk, RAM, tools, recent errors), \
148 topic=storage for all drives with capacity/free space plus large developer cache directories, \
149 topic=hardware for CPU model/cores, RAM size/speed, GPU name/driver, motherboard, BIOS, and display configuration, \
150 topic=updates for Windows Update status (last install date, pending update count, WU service state), \
151 topic=security for Windows Defender real-time protection status, last scan date, signature age, firewall profile states, Windows activation, and UAC state, \
152 topic=pending_reboot to check whether a system restart is required and why (Windows Update, CBS, file rename operations), \
153 topic=disk_health for physical drive health via Get-PhysicalDisk and SMART failure prediction, \
154 topic=battery for charge level, status, estimated runtime, and wear level (laptops only — reports no battery on desktops), \
155 topic=recent_crashes for BSOD and unexpected shutdown events plus application crash/hang events from the Windows event log, \
156 topic=scheduled_tasks for all non-disabled scheduled tasks including name, path, last run time, and executable, \
157 topic=dev_conflicts for cross-tool environment conflict detection (Node.js version managers, Python 2 vs 3 ambiguity, conda env shadowing, Rust toolchain path conflicts, Git identity/signing config, duplicate PATH entries), \
158 topic=bitlocker for drive encryption status (BitLocker on Windows, LUKS on Linux), \
159 topic=rdp for Remote Desktop configuration, port, and active sessions, \
160 topic=shadow_copies for Volume Shadow Copies (VSS) and system restore points, \
161 topic=pagefile for Windows page file configuration and current usage, \
162 topic=windows_features for enabled Windows optional features (IIS, Hyper-V, etc.), \
163 topic=printers for installed printers and active print jobs, \
164 topic=winrm for Windows Remote Management (WinRM) and PS Remoting status, \
165 topic=network_stats for adapter throughput (RX/TX), errors, and dropped packets, \
166 topic=udp_ports for active UDP listeners and notable port annotations, \
167 topic=gpo for applied Group Policy Objects, topic=certificates for local personal certificates, topic=integrity for Windows component store health (SFC/DISM state), topic=domain for Active Directory and domain join status, \
168 topic=device_health for identifying malfunctioning hardware with ConfigManager error codes (Yellow Bangs), topic=drivers for auditing active system drivers and their states, topic=peripherals for enumerating connected USB, input, and display hardware, \
169 topic=sessions for auditing active and disconnected user logon sessions, \
170 topic=disk_benchmark for high-performance silicon-aware stress testing, \
171 and topic=directory or topic=disk for arbitrary paths.",
172 serde_json::json!({
173 "type": "object",
174 "properties": {
175 "topic": {
176 "type": "string",
177 "enum": ["summary", "toolchains", "path", "env_doctor", "fix_plan", "network", "services", "processes", "desktop", "downloads", "directory", "disk", "ports", "repo_doctor", "log_check", "startup_items", "health_report", "storage", "hardware", "updates", "security", "pending_reboot", "disk_health", "battery", "recent_crashes", "scheduled_tasks", "dev_conflicts", "os_config", "bitlocker", "rdp", "shadow_copies", "pagefile", "windows_features", "printers", "winrm", "network_stats", "udp_ports", "gpo", "certificates", "integrity", "domain", "device_health", "drivers", "peripherals", "disk_benchmark"],
178 "description": "Which structured host inspection to run."
179 },
180 "name": {
181 "type": "string",
182 "description": "Optional when topic=processes or topic=services. Case-insensitive substring filter for process or service names."
183 },
184 "issue": {
185 "type": "string",
186 "description": "Optional when topic=fix_plan. Plain-English issue description such as 'cargo not found', 'port 3000 already in use', or 'LM Studio not reachable on localhost:1234'."
187 },
188 "path": {
189 "type": "string",
190 "description": "Required when topic=directory. Optional for topic=disk or topic=repo_doctor. Absolute or relative path to inspect."
191 },
192 "port": {
193 "type": "integer",
194 "description": "Optional when topic=ports or topic=fix_plan. Filter the result to one listening TCP port or anchor a port-conflict fix plan."
195 },
196 "max_entries": {
197 "type": "integer",
198 "description": "Optional cap for listed entries. Defaults to 10 and is capped internally."
199 }
200 }
201 }),
202 ),
203 make_tool(
204 "resolve_host_issue",
205 "A safe, bounded tool for remediating OS and environment issues automatically with user approval. \
206 Use this to fix missing dependencies, restart stuck services, or clear disk space instead of using raw shell. \
207 The user will be prompted to approve the action. Keep targets exact.",
208 serde_json::json!({
209 "type": "object",
210 "properties": {
211 "action": {
212 "type": "string",
213 "enum": ["install_package", "restart_service", "clear_temp"],
214 "description": "The type of remediation to perform."
215 },
216 "target": {
217 "type": "string",
218 "description": "The specific target (e.g., 'python' for install_package, or 'docker' for restart_service). Optional for clear_temp."
219 }
220 },
221 "required": ["action"]
222 }),
223 ),
224 make_tool(
225 "run_hematite_maintainer_workflow",
226 "Run one of Hematite's known maintainer or release workflows with explicit approval. \
227 Prefer this over raw shell when the user explicitly asks to run one of Hematite's own scripts such as `clean.ps1`, `scripts/package-windows.ps1`, or `release.ps1`. \
228 Use workflow=clean for cleanup, workflow=package_windows for rebuilding the local Windows portable or installer, and workflow=release for the normal version bump/tag/push/publish flow. \
229 Keep this tool constrained to Hematite's own known workflows instead of inventing ad hoc shell commands or pretending to run arbitrary project scripts.",
230 serde_json::json!({
231 "type": "object",
232 "properties": {
233 "workflow": {
234 "type": "string",
235 "enum": ["clean", "package_windows", "release"],
236 "description": "Which known Hematite maintainer workflow to run."
237 },
238 "deep": {
239 "type": "boolean",
240 "description": "For workflow=clean. Also remove heavy build/runtime artifacts such as target/ and vein.db."
241 },
242 "reset": {
243 "type": "boolean",
244 "description": "For workflow=clean. Reset PLAN/TASK state in addition to normal cleanup."
245 },
246 "prune_dist": {
247 "type": "boolean",
248 "description": "For workflow=clean. Keep only the current Cargo.toml version under dist/."
249 },
250 "installer": {
251 "type": "boolean",
252 "description": "For workflow=package_windows. Also build the Windows installer."
253 },
254 "add_to_path": {
255 "type": "boolean",
256 "description": "For workflow=package_windows or workflow=release. Update the user PATH to the rebuilt portable."
257 },
258 "version": {
259 "type": "string",
260 "description": "For workflow=release. Exact semantic version such as 0.4.5."
261 },
262 "bump": {
263 "type": "string",
264 "enum": ["patch", "minor", "major"],
265 "description": "For workflow=release. Ask release.ps1 to calculate the next version."
266 },
267 "push": {
268 "type": "boolean",
269 "description": "For workflow=release. Push main and the new tag."
270 },
271 "skip_installer": {
272 "type": "boolean",
273 "description": "For workflow=release. Skip the Windows installer build."
274 },
275 "publish_crates": {
276 "type": "boolean",
277 "description": "For workflow=release. Publish hematite-cli to crates.io after a successful push."
278 },
279 "publish_voice_crate": {
280 "type": "boolean",
281 "description": "For workflow=release. Publish hematite-kokoros first, then hematite-cli."
282 }
283 },
284 "required": ["workflow"]
285 }),
286 ),
287 make_tool(
288 "run_workspace_workflow",
289 "Run an approval-gated workflow or script in the locked project workspace root. \
290 Use this for the current project's build, test, lint, fix, package.json scripts, just/task/make targets, explicit local script paths, or an exact workspace command. \
291 This tool is for the active workspace, not for Hematite's own maintainer scripts.",
292 serde_json::json!({
293 "type": "object",
294 "properties": {
295 "workflow": {
296 "type": "string",
297 "enum": ["build", "test", "lint", "fix", "package_script", "task", "just", "make", "script_path", "command"],
298 "description": "Which workspace workflow to run."
299 },
300 "name": {
301 "type": "string",
302 "description": "Required for workflow=package_script, task, just, or make. The script or target name."
303 },
304 "path": {
305 "type": "string",
306 "description": "Required for workflow=script_path. Relative path to a script inside the locked workspace root."
307 },
308 "command": {
309 "type": "string",
310 "description": "Required for workflow=command. Exact command to execute from the locked workspace root."
311 },
312 "timeout_ms": {
313 "type": "integer",
314 "description": "Optional timeout override in milliseconds."
315 }
316 },
317 "required": ["workflow"]
318 }),
319 ),
320 make_tool(
321 "read_file",
322 "Read the contents of a file. For large files, use 'offset' and 'limit' to navigate.",
323 serde_json::json!({
324 "type": "object",
325 "properties": {
326 "path": {
327 "type": "string",
328 "description": "Path to the file, relative to the project root"
329 },
330 "offset": {
331 "type": "integer",
332 "description": "Starting line number (0-indexed)"
333 },
334 "limit": {
335 "type": "integer",
336 "description": "Number of lines to read"
337 }
338 },
339 "required": ["path"]
340 }),
341 ),
342 make_tool(
343 "lsp_definitions",
344 "Get the precise definition location (file:line:char) for a symbol at a specific position. \
345 Use this to jump to function/struct source code accurately.",
346 serde_json::json!({
347 "type": "object",
348 "properties": {
349 "path": { "type": "string", "description": "File path" },
350 "line": { "type": "integer", "description": "0-indexed line" },
351 "character": { "type": "integer", "description": "0-indexed character" }
352 },
353 "required": ["path", "line", "character"]
354 }),
355 ),
356 make_tool(
357 "lsp_references",
358 "Find all locations where a symbol is used across the entire workspace. \
359 Use this to understand the impact of a refactor or discover internal API users.",
360 serde_json::json!({
361 "type": "object",
362 "properties": {
363 "path": { "type": "string", "description": "File path" },
364 "line": { "type": "integer", "description": "0-indexed line" },
365 "character": { "type": "integer", "description": "0-indexed character" }
366 },
367 "required": ["path", "line", "character"]
368 }),
369 ),
370 make_tool(
371 "lsp_hover",
372 "Get hover information (documentation, function signature, type details) for a symbol. \
373 Use this for rapid spatial awareness without opening every file.",
374 serde_json::json!({
375 "type": "object",
376 "properties": {
377 "path": { "type": "string", "description": "File path" },
378 "line": { "type": "integer", "description": "0-indexed line" },
379 "character": { "type": "integer", "description": "0-indexed character" }
380 },
381 "required": ["path", "line", "character"]
382 }),
383 ),
384 make_tool(
385 "lsp_rename_symbol",
386 "Rename a symbol project-wide using the Language Server. Ensures all references are updated safely.",
387 serde_json::json!({
388 "type": "object",
389 "properties": {
390 "path": { "type": "string", "description": "File path" },
391 "line": { "type": "integer", "description": "0-indexed line" },
392 "character": { "type": "integer", "description": "0-indexed character" },
393 "new_name": { "type": "string", "description": "The new name for the symbol" }
394 },
395 "required": ["path", "line", "character", "new_name"]
396 }),
397 ),
398 make_tool(
399 "lsp_get_diagnostics",
400 "Get a list of current compiler errors and warnings for a specific file. \
401 Use this to verify your code compiles and and to find exactly where errors are located.",
402 serde_json::json!({
403 "type": "object",
404 "properties": {
405 "path": { "type": "string", "description": "File path" }
406 },
407 "required": ["path"]
408 }),
409 ),
410 make_tool(
411 "vision_analyze",
412 "Send an image file (screenshot, diagram, or UI mockup) to the multimodal vision model for technical analysis. \
413 Use this to identify UI bugs, confirm visual states, or understand architectural diagrams.",
414 serde_json::json!({
415 "type": "object",
416 "properties": {
417 "path": { "type": "string", "description": "Absolute or relative path to the image file." },
418 "prompt": { "type": "string", "description": "The specific question or analysis request for the vision model." }
419 },
420 "required": ["path", "prompt"]
421 }),
422 ),
423 make_tool(
424 "patch_hunk",
425 "Replace a specific line range [start_line, end_line] with new content. \
426 This is the most precise way to edit code and avoids search string failures.",
427 serde_json::json!({
428 "type": "object",
429 "properties": {
430 "path": { "type": "string", "description": "File path" },
431 "start_line": { "type": "integer", "description": "Starting line (1-indexed)" },
432 "end_line": { "type": "integer", "description": "Ending line (inclusive)" },
433 "replacement": { "type": "string", "description": "The new content for this range" }
434 },
435 "required": ["path", "start_line", "end_line", "replacement"]
436 }),
437 ),
438 make_tool(
439 "multi_search_replace",
440 "Replace multiple existing code blocks in a single file with new content. \
441 Each hunk specifies an EXACT 'search' string and a 'replace' string. \
442 The 'search' string MUST exactly match the existing file contents (including whitespace). \
443 This is the safest and most reliable way to make multiple structural edits.",
444 serde_json::json!({
445 "type": "object",
446 "properties": {
447 "path": { "type": "string", "description": "File path" },
448 "hunks": {
449 "type": "array",
450 "items": {
451 "type": "object",
452 "properties": {
453 "search": { "type": "string", "description": "Exact existing text to find and replace" },
454 "replace": { "type": "string", "description": "The new replacement text" }
455 },
456 "required": ["search", "replace"]
457 }
458 }
459 },
460 "required": ["path", "hunks"]
461 }),
462 ),
463 make_tool(
464 "write_file",
465 "Write content to a file, creating it (and any parent dirs) if needed. \
466 Overwrites existing files.",
467 serde_json::json!({
468 "type": "object",
469 "properties": {
470 "path": { "type": "string", "description": "File path" },
471 "content": { "type": "string", "description": "Full file content to write" }
472 },
473 "required": ["path", "content"]
474 }),
475 ),
476 make_tool(
477 "research_web",
478 "Perform a zero-cost technical search using DuckDuckGo. \
479 Use this to find documentation, latest API changes, or solutions to complex errors \
480 when your internal knowledge is insufficient. Returns snippets and URLs.",
481 serde_json::json!({
482 "type": "object",
483 "properties": {
484 "query": { "type": "string", "description": "The technical search query" }
485 },
486 "required": ["query"]
487 }),
488 ),
489 make_tool(
490 "fetch_docs",
491 "Fetch a URL and convert it to clean Markdown. Use this to 'read' the documentation \
492 links found via research_web. This tool uses a proxy to bypass IP blocks.",
493 serde_json::json!({
494 "type": "object",
495 "properties": {
496 "url": { "type": "string", "description": "The URL of the documentation to fetch" }
497 },
498 "required": ["url"]
499 }),
500 ),
501 make_tool(
502 "edit_file",
503 "Edit a file by replacing an exact string with another. \
504 The 'search' string does NOT need perfectly matching indentation (it is fuzzy), \
505 but the non-whitespace text must match exactly. Use this for targeted edits.",
506 serde_json::json!({
507 "type": "object",
508 "properties": {
509 "path": { "type": "string", "description": "File path" },
510 "search": {
511 "type": "string",
512 "description": "The exact text to find (must match whitespace/indentation precisely)"
513 },
514 "replace": {
515 "type": "string",
516 "description": "The replacement text"
517 }
518 },
519 "required": ["path", "search", "replace"]
520 }),
521 ),
522 make_tool(
523 "auto_pin_context",
524 "Select 1-3 core files to 'Lock' into high-fidelity memory. \
525 Use this to ensure the most important architecture files \
526 are always visible during complex refactorings.",
527 serde_json::json!({
528 "type": "object",
529 "properties": {
530 "paths": {
531 "type": "array",
532 "items": { "type": "string" }
533 },
534 "reason": { "type": "string" }
535 },
536 "required": ["paths", "reason"]
537 }),
538 ),
539 make_tool(
540 "list_pinned",
541 "List all files currently pinned in the model's active context.",
542 serde_json::json!({
543 "type": "object",
544 "properties": {}
545 }),
546 ),
547 make_tool(
548 "list_files",
549 "List files in a directory, optionally filtered by extension.",
550 serde_json::json!({
551 "type": "object",
552 "properties": {
553 "path": {
554 "type": "string",
555 "description": "Directory to list (default: current dir)"
556 },
557 "extension": {
558 "type": "string",
559 "description": "Only return files with this extension, e.g. 'rs', 'toml' (no dot)"
560 }
561 },
562 "required": []
563 }),
564 ),
565 make_tool(
566 "tail_file",
567 "Read the last N lines of a file — useful for log files, test output, \
568 build artifacts, and any large file where only the tail is relevant. \
569 Supports an optional grep filter to show only matching lines from the tail. \
570 Use this instead of read_file when you only need the end of a large file.",
571 serde_json::json!({
572 "type": "object",
573 "properties": {
574 "path": {
575 "type": "string",
576 "description": "Path to the file, relative to the project root"
577 },
578 "lines": {
579 "type": "integer",
580 "description": "Number of lines to return from the end (default: 50, max: 500)"
581 },
582 "grep": {
583 "type": "string",
584 "description": "Optional regex pattern — only return lines matching this pattern (applied before the tail slice)"
585 }
586 },
587 "required": ["path"]
588 }),
589 ),
590 make_tool(
591 "grep_files",
592 "Search file contents for a regex pattern. Supports context lines, files-only mode, \
593 and pagination. Returns file:line:content format by default.",
594 serde_json::json!({
595 "type": "object",
596 "properties": {
597 "pattern": {
598 "type": "string",
599 "description": "Regex pattern to search for (case-insensitive by default)"
600 },
601 "path": {
602 "type": "string",
603 "description": "Directory to search (default: current dir)"
604 },
605 "extension": {
606 "type": "string",
607 "description": "Only search files with this extension, e.g. 'rs'"
608 },
609 "mode": {
610 "type": "string",
611 "enum": ["content", "files_only"],
612 "description": "'content' (default) returns matching lines; 'files_only' returns only filenames"
613 },
614 "context": {
615 "type": "integer",
616 "description": "Lines of context before AND after each match (like rg -C)"
617 },
618 "before": {
619 "type": "integer",
620 "description": "Lines of context before each match (overrides context)"
621 },
622 "after": {
623 "type": "integer",
624 "description": "Lines of context after each match (overrides context)"
625 },
626 "head_limit": {
627 "type": "integer",
628 "description": "Max hunks (or files in files_only) to return (default: 50)"
629 },
630 "offset": {
631 "type": "integer",
632 "description": "Skip first N hunks/files - for pagination (default: 0)"
633 }
634 },
635 "required": ["pattern"]
636 }),
637 ),
638 make_tool(
639 "git_commit",
640 "Stage all changes (git add -A) and create a commit. You MUST use 'Conventional Commits' (e.g. 'feat: description').",
641 serde_json::json!({
642 "type": "object",
643 "properties": {
644 "message": { "type": "string", "description": "Commit message (Conventional Commit style)" }
645 },
646 "required": ["message"]
647 }),
648 ),
649 make_tool(
650 "git_push",
651 "Push current branched changes to the remote origin. Requires an existing remote connection.",
652 serde_json::json!({
653 "type": "object",
654 "properties": {},
655 "required": []
656 }),
657 ),
658 make_tool(
659 "git_remote",
660 "View or manage git remotes. Use this for onboarding to GitHub/GitLab services.",
661 serde_json::json!({
662 "type": "object",
663 "properties": {
664 "action": {
665 "type": "string",
666 "enum": ["list", "add", "remove"],
667 "description": "Operation to perform"
668 },
669 "name": { "type": "string", "description": "Remote name (e.g. origin)" },
670 "url": { "type": "string", "description": "Remote URL (for 'add' action)" }
671 },
672 "required": ["action"]
673 }),
674 ),
675 make_tool(
676 "git_onboarding",
677 "High-level wizard to connect this repository to a remote host (GitHub/GitLab). \
678 Handles adding the remote and performing the initial tracking push in one step.",
679 serde_json::json!({
680 "type": "object",
681 "properties": {
682 "url": { "type": "string", "description": "The remote repository URL (HTTPS or SSH)" },
683 "name": { "type": "string", "description": "The remote name (default: origin)" },
684 "push": { "type": "boolean", "description": "Whether to perform an initial push to establish tracking (default: false)" }
685 },
686 "required": ["url"]
687 }),
688 ),
689 make_tool(
690 "verify_build",
691 "Run project verification for build, test, lint, or fix workflows. \
692 Prefer per-project verify profiles from `.hematite/settings.json`, and fall back to \
693 auto-detected defaults when no profile is configured. Returns BUILD OK or BUILD FAILED \
694 with command output. ALWAYS call this after scaffolding a new project or making structural changes.",
695 serde_json::json!({
696 "type": "object",
697 "properties": {
698 "action": {
699 "type": "string",
700 "enum": ["build", "test", "lint", "fix"],
701 "description": "Which verification action to run. Defaults to build."
702 },
703 "profile": {
704 "type": "string",
705 "description": "Optional named verify profile from `.hematite/settings.json`."
706 },
707 "timeout_secs": {
708 "type": "integer",
709 "description": "Optional timeout override for this verification run."
710 }
711 }
712 }),
713 ),
714 make_tool(
715 "git_worktree",
716 "Manage Git worktrees - isolated working directories on separate branches. \
717 Use 'add' to create a safe sandbox for risky/experimental work, \
718 'list' to see all worktrees, 'remove' to clean up, 'prune' to remove stale entries.",
719 serde_json::json!({
720 "type": "object",
721 "properties": {
722 "action": {
723 "type": "string",
724 "enum": ["list", "add", "remove", "prune"],
725 "description": "Worktree operation to perform"
726 },
727 "path": {
728 "type": "string",
729 "description": "Directory path for the new worktree (required for add/remove)"
730 },
731 "branch": {
732 "type": "string",
733 "description": "Branch name for the worktree (add only; defaults to path basename)"
734 }
735 },
736 "required": ["action"]
737 }),
738 ),
739 make_tool(
740 "clarify",
741 "Ask the user a clarifying question when you genuinely cannot proceed without \
742 more information. Use this ONLY when you are blocked and cannot make a \
743 reasonable assumption. Do NOT use it to ask permission - just act.",
744 serde_json::json!({
745 "type": "object",
746 "properties": {
747 "question": {
748 "type": "string",
749 "description": "The specific question to ask the user"
750 }
751 },
752 "required": ["question"]
753 }),
754 ),
755 make_tool(
756 "manage_tasks",
757 "Manage the persistent task ledger in .hematite/TASK.md. Use this to track long-term goals across restarts.",
758 crate::tools::tasks::get_tasks_params(),
759 ),
760 make_tool(
761 "maintain_plan",
762 "Document the architectural strategy and session blueprint in .hematite/PLAN.md. Use this to maintain context across restarts.",
763 crate::tools::plan::get_plan_params(),
764 ),
765 make_tool(
766 "generate_walkthrough",
767 "Generate a final session report in .hematite/WALKTHROUGH.md including achievements and verification results.",
768 crate::tools::plan::get_walkthrough_params(),
769 ),
770 make_tool(
771 "swarm",
772 "Delegate high-volume parallel tasks to a swarm of background workers. \
773 Use this for large-scale refactors, multi-file research, or parallel documentation updates. \
774 You must provide a 'tasks' array where each task has an 'id', 'target' (file), and 'instruction'.",
775 serde_json::json!({
776 "type": "object",
777 "properties": {
778 "tasks": {
779 "type": "array",
780 "items": {
781 "type": "object",
782 "properties": {
783 "id": { "type": "string" },
784 "target": { "type": "string", "description": "Target file or directory" },
785 "instruction": { "type": "string", "description": "Specific task for this worker" }
786 },
787 "required": ["id", "target", "instruction"]
788 }
789 },
790 "max_workers": {
791 "type": "integer",
792 "description": "Max parallel workers (default 3, auto-throttled by hardware)",
793 "default": 3
794 }
795 },
796 "required": ["tasks"]
797 }),
798 ),
799 ];
800
801 let lsp_defs = crate::tools::lsp_tools::get_lsp_definitions();
802 tools.push(make_tool(
803 "lsp_search_symbol",
804 "Find the location (file/line) of any function, struct, or variable in the entire project workspace. \
805 This is the fastest 'Golden Path' for navigating to a symbol by name.",
806 serde_json::json!({
807 "type": "object",
808 "properties": {
809 "query": { "type": "string", "description": "The name of the symbol to find (e.g. 'initialize_mcp')" }
810 },
811 "required": ["query"]
812 }),
813 ));
814 for def in lsp_defs {
815 let name = def["name"].as_str().unwrap();
816 tools.push(ToolDefinition {
817 tool_type: "function".into(),
818 function: ToolFunction {
819 name: name.into(),
820 description: def["description"].as_str().unwrap().into(),
821 parameters: def["parameters"].clone(),
822 },
823 metadata: tool_metadata_for_name(name),
824 });
825 }
826
827 tools
828}
829
830pub async fn dispatch_builtin_tool(name: &str, args: &Value) -> Result<String, String> {
831 match name {
832 "shell" => crate::tools::shell::execute(args).await,
833 "run_code" => crate::tools::code_sandbox::execute(args).await,
834 "trace_runtime_flow" => crate::tools::runtime_trace::trace_runtime_flow(args).await,
835 "describe_toolchain" => crate::tools::toolchain::describe_toolchain(args).await,
836 "inspect_host" => crate::tools::host_inspect::inspect_host(args).await,
837 "resolve_host_issue" => crate::tools::host_inspect::resolve_host_issue(args).await,
838 "run_hematite_maintainer_workflow" => {
839 crate::tools::repo_script::run_hematite_maintainer_workflow(args).await
840 }
841 "run_workspace_workflow" => crate::tools::workspace_workflow::run_workspace_workflow(args).await,
842 "read_file" => crate::tools::file_ops::read_file(args).await,
843 "inspect_lines" => crate::tools::file_ops::inspect_lines(args).await,
844 "tail_file" => crate::tools::file_ops::tail_file(args).await,
845 "write_file" => crate::tools::file_ops::write_file(args).await,
846 "edit_file" => crate::tools::file_ops::edit_file(args).await,
847 "patch_hunk" => crate::tools::file_ops::patch_hunk(args).await,
848 "multi_search_replace" => crate::tools::file_ops::multi_search_replace(args).await,
849 "list_files" => crate::tools::file_ops::list_files(args).await,
850 "grep_files" => crate::tools::file_ops::grep_files(args).await,
851 "git_commit" => crate::tools::git::execute(args).await,
852 "git_push" => crate::tools::git::execute_push(args).await,
853 "git_remote" => crate::tools::git::execute_remote(args).await,
854 "git_onboarding" => crate::tools::git_onboarding::execute(args).await,
855 "verify_build" => crate::tools::verify_build::execute(args).await,
856 "git_worktree" => crate::tools::git::execute_worktree(args).await,
857 "health" => crate::tools::health::execute(args).await,
858 "research_web" => crate::tools::research::execute_search(args).await,
859 "fetch_docs" => crate::tools::research::execute_fetch(args).await,
860 "manage_tasks" => crate::tools::tasks::manage_tasks(args).await,
861 "maintain_plan" => crate::tools::plan::maintain_plan(args).await,
862 "generate_walkthrough" => crate::tools::plan::generate_walkthrough(args).await,
863 "clarify" => {
864 let q = args.get("question").and_then(|v| v.as_str()).unwrap_or("?");
865 Ok(format!("[clarify] {q}"))
866 }
867 "vision_analyze" => Err(
868 "Tool 'vision_analyze' must be dispatched by ConversationManager (it requires hardware engine access)."
869 .into(),
870 ),
871 other => {
872 if other.contains('.') || other.contains('/') || other.contains('\\') {
873 Err(format!(
874 "'{}' is a PATH, not a tool. You correctly identified the location, but you MUST use `read_file` or `list_files` (internal) or `powershell` (external) to access it.",
875 other
876 ))
877 } else if matches!(other.to_lowercase().as_str(), "hematite" | "assistant" | "ai") {
878 Err(format!(
879 "'{}' is YOUR IDENTITY, not a tool. Use list_files or read_file to explore the codebase.",
880 other
881 ))
882 } else if matches!(
883 other.to_lowercase().as_str(),
884 "thought" | "think" | "reasoning" | "thinking" | "internal"
885 ) {
886 Err(format!(
887 "'{}' is NOT a tool - it is a reasoning tag. Output your answer as plain text after your <think> block.",
888 other
889 ))
890 } else {
891 Err(format!("Unknown tool: '{}'", other))
892 }
893 }
894 }
895}