Skip to main content

hematite/agent/
routing.rs

1use super::conversation::WorkflowMode;
2
3#[derive(Clone, Copy, Debug, Eq, PartialEq)]
4pub enum QueryIntentClass {
5    ProductTruth,
6    RuntimeDiagnosis,
7    RepoArchitecture,
8    Toolchain,
9    Capability,
10    Implementation,
11    Unknown,
12}
13
14#[derive(Clone, Copy, Debug, Eq, PartialEq)]
15pub enum DirectAnswerKind {
16    About,
17    LanguageCapability,
18    UnsafeWorkflowPressure,
19    SessionMemory,
20    RecoveryRecipes,
21    McpLifecycle,
22    AuthorizationPolicy,
23    ToolClasses,
24    ToolRegistryOwnership,
25    SessionResetSemantics,
26    ProductSurface,
27    ReasoningSplit,
28    Identity,
29    WorkflowModes,
30    GemmaNative,
31    GemmaNativeSettings,
32    VerifyProfiles,
33    Toolchain,
34    HostInspection,
35    ArchitectSessionResetPlan,
36}
37
38#[derive(Clone, Copy, Debug)]
39pub struct QueryIntent {
40    pub primary_class: QueryIntentClass,
41    pub direct_answer: Option<DirectAnswerKind>,
42    pub grounded_trace_mode: bool,
43    pub capability_mode: bool,
44    pub capability_needs_repo: bool,
45    pub toolchain_mode: bool,
46    pub host_inspection_mode: bool,
47    pub maintainer_workflow_mode: bool,
48    pub workspace_workflow_mode: bool,
49    pub architecture_overview_mode: bool,
50    pub sovereign_mode: bool,
51    pub surgical_filesystem_mode: bool,
52}
53
54fn contains_any(haystack: &str, needles: &[&str]) -> bool {
55    needles.iter().any(|needle| haystack.contains(needle))
56}
57
58fn contains_all(haystack: &str, needles: &[&str]) -> bool {
59    needles.iter().all(|needle| haystack.contains(needle))
60}
61
62fn mentions_reset_commands(lower: &str) -> bool {
63    contains_all(lower, &["/clear", "/new", "/forget"])
64}
65
66fn mentions_stable_product_surface(lower: &str) -> bool {
67    contains_any(
68        lower,
69        &[
70            "stable product-surface question",
71            "stable product surface question",
72            "stable product-surface questions",
73            "stable product surface questions",
74        ],
75    )
76}
77
78fn mentions_product_truth_routing(lower: &str) -> bool {
79    let asks_decision_policy = contains_any(
80        lower,
81        &[
82            "how hematite decides",
83            "how does hematite decide",
84            "decides whether",
85            "decide whether",
86        ],
87    );
88    let asks_direct_vs_inspect_split = contains_any(
89        lower,
90        &[
91            "answered as stable product truth",
92            "stable product truth",
93            "stable product behavior",
94            "answer directly",
95            "direct answer",
96            "inspect the repository",
97            "inspect repository",
98            "repository implementation",
99            "repo implementation",
100        ],
101    );
102    asks_decision_policy && asks_direct_vs_inspect_split
103}
104
105fn mentions_broad_system_walkthrough(lower: &str) -> bool {
106    let asks_walkthrough = contains_any(
107        lower,
108        &[
109            "walk me through",
110            "walk through",
111            "how hematite is wired",
112            "understand how hematite is wired",
113            "major runtime pieces",
114            "normal message moves",
115            "moves from the tui to the model and back",
116        ],
117    );
118    let asks_multiple_runtime_areas = contains_any(
119        lower,
120        &[
121            "session recovery",
122            "tool policy",
123            "mcp state",
124            "mcp policy",
125            "files own the major runtime pieces",
126            "which files own",
127            "where session recovery",
128            "where tool policy",
129            "where mcp state",
130        ],
131    );
132    asks_walkthrough && asks_multiple_runtime_areas
133}
134
135fn mentions_capability_question(lower: &str) -> bool {
136    contains_any(
137        lower,
138        &[
139            "what can you do",
140            "what are you capable",
141            "can you make projects",
142            "can you build projects",
143            "do you know other coding languages",
144            "other coding languages",
145            "what languages",
146            "can you use the internet",
147            "internet research capabilities",
148            "what tools do you have",
149        ],
150    )
151}
152
153fn mentions_creator_question(lower: &str) -> bool {
154    contains_any(
155        lower,
156        &[
157            "who created you",
158            "who built you",
159            "who made you",
160            "who developed you",
161            "who engineered you",
162            "who engineered your architecture",
163            "who created hematite",
164            "who built hematite",
165            "who developed hematite",
166            "who engineered hematite",
167            "who maintains hematite",
168            "who authored hematite",
169            "who is the author",
170            "who wrote this",
171            "who made this app",
172        ],
173    )
174}
175
176fn capability_question_requires_repo_inspection(lower: &str) -> bool {
177    contains_any(
178        lower,
179        &[
180            "this repo",
181            "this repository",
182            "codebase",
183            "which files",
184            "implementation",
185            "in this project",
186        ],
187    )
188}
189
190fn mentions_host_inspection_question(lower: &str) -> bool {
191    let host_scope = contains_any(
192        lower,
193        &[
194            "path",
195            "package manager",
196            "package managers",
197            "env doctor",
198            "environment doctor",
199            "pip",
200            "winget",
201            "choco",
202            "scoop",
203            "network",
204            "adapter",
205            "dns",
206            "gateway",
207            "ip address",
208            "ipconfig",
209            "wifi",
210            "ethernet",
211            "service",
212            "services",
213            "daemon",
214            "startup type",
215            "process",
216            "processes",
217            "task manager",
218            "ram",
219            "cpu",
220            "gpu",
221            "vram",
222            "nvidia",
223            "memory",
224            "developer tools",
225            "toolchains",
226            "installed",
227            "desktop",
228            "downloads",
229            "folder",
230            "directory",
231            "local development",
232            "machine",
233            "computer",
234            "firewall",
235            "vpn",
236            "proxy",
237            "internet",
238            "online",
239            "connectivity",
240            "ssid",
241            "wireless",
242            "tcp connection",
243            "active connection",
244            "traceroute",
245            "tracert",
246            "dns cache",
247            "arp table",
248            "arp cache",
249            "route table",
250            "routing table",
251            "default gateway",
252            "next hop",
253            "power plan",
254            "power settings",
255            "uptime",
256            "reboot",
257            "silicon",
258            "throttle",
259            "throttled",
260            "clocks",
261            "mhz",
262            "health",
263            "report",
264            "bitlocker",
265            "rdp",
266            "remote desktop",
267            "vss",
268            "shadow copy",
269            "shadow copies",
270            "pagefile",
271            "virtual memory",
272            "swap",
273            "windows feature",
274            "optional feature",
275            "printer",
276            "print queue",
277            "audio",
278            "sound",
279            "speaker",
280            "speakers",
281            "microphone",
282            "mic",
283            "bluetooth",
284            "pairing",
285            "headset",
286            "headphones",
287            "camera",
288            "webcam",
289            "windows hello",
290            "hello not working",
291            "sign in issue",
292            "search index",
293            "windows search",
294            "indexer",
295            "winrm",
296            "psremoting",
297            "network stats",
298            "adapter stats",
299            "udp listening",
300            "udp port",
301            "session",
302            "logon",
303            "login",
304            "slat",
305            "error",
306            "warning",
307            "event",
308            "log",
309            "throughput",
310            "permission",
311            "access control",
312            "login",
313            "logon",
314            "registry",
315            "share",
316            "mbps",
317            "ad",
318            "sid",
319            "vm",
320            "hyper-v",
321            "hyperv",
322            "dhcp",
323            "lease",
324        ],
325    );
326    let host_action = contains_any(
327        lower,
328        &[
329            "inspect",
330            "count",
331            "tell me",
332            "summarize",
333            "how big",
334            "biggest",
335            "versions",
336            "duplicate",
337            "analyze",
338            "missing",
339            "ready",
340            "resolve",
341            "troubleshoot",
342            "show me",
343            "show",
344            "find",
345            "list",
346            "audit",
347            "test",
348            "check",
349            "is",
350            "are",
351            "am",
352            "why",
353            "what",
354            "currently",
355            "status",
356            "stats",
357            "vitals",
358            "telemetry",
359            "how",
360            "looking",
361        ],
362    );
363
364    host_scope && host_action
365}
366
367pub fn preferred_host_inspection_topic(user_input: &str) -> Option<&'static str> {
368    let lower = user_input.to_lowercase();
369    let asks_fix_plan = (lower.contains("fix")
370        || lower.contains("repair")
371        || lower.contains("resolve")
372        || lower.contains("troubleshoot"))
373        && (lower.contains("cargo")
374            || lower.contains("path")
375            || lower.contains("package manager")
376            || lower.contains("toolchain")
377            || lower.contains("port ")
378            || lower.contains("already in use")
379            || lower.contains("lm studio")
380            || lower.contains("localhost:1234")
381            || lower.contains("embedding model")
382            || lower.contains("no coding model loaded"));
383    let asks_path = lower.contains("path entries")
384        || lower.contains("raw path")
385        || (lower.contains("path") && (lower.contains("show") || lower.contains("what is")));
386    let asks_gpo = lower.contains("gpo")
387        || lower.contains("group policy")
388        || lower.contains("gpresult")
389        || lower.contains("applied policy");
390    let asks_certificates = lower.contains("cert")
391        || lower.contains("ssl")
392        || lower.contains("client cert")
393        || lower.contains("expiring cert");
394    let asks_integrity = lower.contains("integrity")
395        || lower.contains("sfc")
396        || lower.contains("dism")
397        || lower.contains("corruption")
398        || lower.contains("os health");
399    let asks_user_accounts = lower.contains("user account")
400        || lower.contains("local user")
401        || lower.contains("local group")
402        || lower.contains("get-localuser")
403        || lower.contains("get-localgroup")
404        || lower.contains("get-localgroupmember")
405        || lower.contains("who is logged in")
406        || lower.contains("who is logged on")
407        || lower.contains("who am i")
408        || lower.contains("logged in as")
409        || lower.contains("logged in user")
410        || lower.contains("logged on user")
411        || lower.contains("admin group")
412        || lower.contains("administrators group")
413        || lower.contains("local admin")
414        || lower.contains("who has admin")
415        || lower.contains("running as admin")
416        || lower.contains("is this elevated")
417        || lower.contains("active sessions")
418        || lower.contains("logon session")
419        || lower.contains("net user")
420        || lower.contains("net localgroup");
421    let asks_ad_user = lower.contains("ad user")
422        || lower.contains("domain user")
423        || (lower.contains("user") && (lower.contains("sid") || lower.contains("membership")));
424    let asks_dns_lookup = (lower.contains("dns") || lower.contains("record"))
425        && (lower.contains("lookup")
426            || lower.contains("srv")
427            || lower.contains("mx")
428            || lower.contains("txt"));
429    let asks_hyperv =
430        lower.contains("hyper-v") || lower.contains("hyperv") || lower.contains("list vm");
431    let asks_ip_config =
432        lower.contains("ipconfig") && (lower.contains("all") || lower.contains("detailed"));
433    let asks_domain = lower.contains("domain")
434        || lower.contains("active directory")
435        || lower.contains("ad join")
436        || lower.contains("workgroup");
437    let asks_device_health = lower.contains("device health")
438        || lower.contains("hardware error")
439        || lower.contains("malfunctioning")
440        || lower.contains("yellow bang")
441        || lower.contains("hardware failing");
442    let asks_drivers =
443        lower.contains("driver") || lower.contains("kmod") || lower.contains("kernel module");
444    let asks_audio = lower.contains("no sound")
445        || lower.contains("audio service")
446        || lower.contains("windows audio")
447        || lower.contains("speaker")
448        || lower.contains("speakers")
449        || lower.contains("microphone")
450        || lower.contains(" mic ")
451        || lower.starts_with("mic ")
452        || lower.contains("mic not")
453        || lower.contains("headset")
454        || lower.contains("headphones")
455        || lower.contains("playback device")
456        || lower.contains("recording device")
457        || lower.contains("audio endpoint")
458        || lower.contains("audioendpointbuilder")
459        || ((lower.contains("audio") || lower.contains("sound"))
460            && (lower.contains("device")
461                || lower.contains("driver")
462                || lower.contains("service")
463                || lower.contains("working")
464                || lower.contains("broken")
465                || lower.contains("input")
466                || lower.contains("output")
467                || lower.contains("crackling")
468                || lower.contains("mute")
469                || lower.contains("muted")
470                || lower.contains("volume")
471                || lower.contains("speaker")
472                || lower.contains("microphone")))
473            && !lower.contains("audio file")
474            && !lower.contains("voice engine");
475    let asks_bluetooth = lower.contains("bluetooth")
476        || lower.contains("pairing")
477        || lower.contains("paired device")
478        || lower.contains("paired devices")
479        || lower.contains("bthserv")
480        || lower.contains("bthavctpsvc")
481        || lower.contains("btagservice")
482        || lower.contains("bluetoothuserservice")
483        || lower.contains("wireless headset")
484        || lower.contains("wireless earbuds")
485        || ((lower.contains("headset") || lower.contains("headphones"))
486            && (lower.contains("disconnect")
487                || lower.contains("pair")
488                || lower.contains("reconnect")
489                || lower.contains("bluetooth")))
490        || ((lower.contains("won't") || lower.contains("cannot") || lower.contains("can't"))
491            && (lower.contains("pair") || lower.contains("connect"))
492            && lower.contains("bluetooth"));
493    let asks_camera = lower.contains("camera")
494        || lower.contains("webcam")
495        || lower.contains("web cam")
496        || (lower.contains("app") && lower.contains("can't see") && lower.contains("camera"))
497        || (lower.contains("camera") && lower.contains("permission"))
498        || (lower.contains("camera") && lower.contains("privacy"))
499        || (lower.contains("camera") && lower.contains("not working"))
500        || (lower.contains("camera") && lower.contains("missing"))
501        || lower.contains("camera_privacy");
502    let asks_sign_in = lower.contains("windows hello")
503        || (lower.contains("hello") && lower.contains("not working"))
504        || (lower.contains("pin")
505            && (lower.contains("broken")
506                || lower.contains("not working")
507                || lower.contains("forgot")))
508        || (lower.contains("can't sign in")
509            || lower.contains("cannot sign in")
510            || lower.contains("cant sign in"))
511        || (lower.contains("sign") && lower.contains("in") && lower.contains("issue"))
512        || lower.contains("logon failure")
513        || lower.contains("credential provider")
514        || lower.contains("biometric service")
515        || (lower.contains("profile") && lower.contains("corrupt"))
516        || lower.contains("wbiosrvc");
517    let asks_search_index = (lower.contains("search")
518        && (lower.contains("broken")
519            || lower.contains("not working")
520            || lower.contains("slow")
521            || lower.contains("indexing")
522            || lower.contains("index")))
523        || lower.contains("wsearch")
524        || lower.contains("windows search")
525        || lower.contains("search index")
526        || lower.contains("indexer")
527        || (lower.contains("search") && lower.contains("stuck"))
528        || (lower.contains("search") && lower.contains("results") && lower.contains("show"));
529    let asks_peripherals = lower.contains("peripheral")
530        || lower.contains("usb")
531        || lower.contains("keyboard")
532        || lower.contains("mouse")
533        || lower.contains("pointer")
534        || lower.contains("monitor")
535        || lower.contains("input device")
536        || lower.contains("connected hardware");
537    let asks_sessions = lower.contains("session")
538        || lower.contains("login")
539        || lower.contains("who is on")
540        || lower.contains("active user");
541    let asks_virtualization = lower.contains("virtualization")
542        || lower.contains("hypervisor")
543        || lower.contains("vt-x")
544        || lower.contains("slat")
545        || lower.contains("v-p")
546        || lower.contains("nested virt")
547        || lower.contains("cpu model")
548        || lower.contains("ram size")
549        || lower.contains("hardware spec")
550        || lower.contains("hardware dna")
551        || lower.contains("hardware info")
552        || lower.contains("bios version")
553        || lower.contains("motherboard")
554        || lower.contains("how much ram")
555        || lower.contains("what processor")
556        || lower.contains("what cpu")
557        || (lower.contains("what hardware") && lower.contains("have"))
558        || (lower.contains("hardware") && lower.contains("inventory"));
559    let asks_startup = lower.contains("startup")
560        || lower.contains("boot program")
561        || lower.contains("autorun")
562        || lower.contains("run at boot");
563    let asks_env_doctor = lower.contains("env doctor")
564        || lower.contains("environment doctor")
565        || lower.contains("package manager")
566        || lower.contains("package managers")
567        || lower.contains("shims")
568        || lower.contains("path drift")
569        || lower.contains("environment is broken")
570        || lower.contains("env is broken")
571        || (lower.contains("dev machine") && lower.contains("off"))
572        || (lower.contains("environment") && lower.contains("sane"));
573    let asks_lan_discovery = lower.contains("upnp")
574        || lower.contains("ssdp")
575        || lower.contains("mdns")
576        || lower.contains("bonjour")
577        || lower.contains("llmnr")
578        || lower.contains("network neighborhood")
579        || lower.contains("device discovery")
580        || lower.contains("local discovery")
581        || lower.contains("discover local devices")
582        || lower.contains("discover devices")
583        || lower.contains("browse computers")
584        || (lower.contains("local network")
585            && (lower.contains("discover")
586                || lower.contains("discovery")
587                || lower.contains("neighborhood")
588                || lower.contains("device")
589                || lower.contains("devices")
590                || lower.contains("aware of")))
591        || ((lower.contains("netbios") || lower.contains("smb visibility"))
592            && !lower.contains("active directory"))
593        || ((lower.contains("nas")
594            || lower.contains("printer")
595            || lower.contains("device")
596            || lower.contains("computer")
597            || lower.contains("pc"))
598            && ((lower.contains("can't") && lower.contains("see"))
599                || (lower.contains("cannot") && lower.contains("see"))
600                || (lower.contains("cant") && lower.contains("see"))
601                || lower.contains("can't see")
602                || lower.contains("cannot see")
603                || lower.contains("cant see")
604                || lower.contains("not visible")
605                || lower.contains("not showing up")
606                || lower.contains("not show up")
607                || lower.contains("discover"))
608            && (lower.contains("network")
609                || lower.contains("lan")
610                || lower.contains("local")
611                || lower.contains("neighborhood")));
612    let asks_network = (((lower.contains("network") && !lower.contains("active directory"))
613        && !lower.contains("stat")
614        && !lower.contains("share")
615        && !lower.contains("throughput"))
616        || lower.contains("adapter")
617        || lower.contains("ip address")
618        || lower.contains("ipconfig")
619        || lower.contains("ipv4")
620        || lower.contains("ipv6")
621        || lower.contains("subnet")
622        || lower.contains("dns server")
623        || lower.contains("nameserver")
624        || lower.contains("wifi")
625        || lower.contains("wireless")
626        || lower.contains("ethernet")
627        || lower.contains("lan"))
628        && !asks_ad_user;
629    let asks_services = lower.contains("service")
630        || lower.contains("services")
631        || lower.contains("daemon")
632        || lower.contains("startup type")
633        || lower.contains("background service")
634        || lower.contains("windows service")
635        || lower.contains("systemctl")
636        || lower.contains("get-service");
637    let asks_processes = lower.contains("process")
638        || lower.contains("processes")
639        || lower.contains("task manager")
640        || lower.contains("what is running")
641        || lower.contains("what's running")
642        || lower.contains("using my ram")
643        || lower.contains("using ram")
644        || lower.contains("using my cpu")
645        || lower.contains("top memory")
646        || lower.contains("top ram")
647        || lower.contains("high memory")
648        || lower.contains("resource-heavy processes")
649        || lower.contains("heavy hitters")
650        || (lower.contains("using the most")
651            && (lower.contains("cpu") || lower.contains("ram") || lower.contains("memory")))
652        || (lower.contains("most cpu")
653            || lower.contains("most ram")
654            || lower.contains("most memory"))
655        || (lower.contains("hitting")
656            && (lower.contains("cpu") || lower.contains("ram") || lower.contains("disk")));
657    let asks_toolchains = lower.contains("developer tools")
658        || lower.contains("toolchains")
659        || (lower.contains("installed") && lower.contains("version"))
660        || (lower.contains("detect") && lower.contains("version"));
661    let _asks_permissions = lower.contains("permission")
662        || lower.contains("access control")
663        || lower.contains("get-acl")
664        || lower.contains("acl ")
665        || lower.contains("icacls")
666        || lower.contains("takeown")
667        || lower.contains("ntfs permission")
668        || (lower.contains("who has") && lower.contains("access"));
669    let _asks_login_history = lower.contains("login history")
670        || lower.contains("logon history")
671        || lower.contains("who logged in")
672        || lower.contains("recent logon")
673        || lower.contains("failed logon")
674        || lower.contains("event id 4624")
675        || lower.contains("eventid 4624");
676    let _asks_registry_audit = lower.contains("registry audit")
677        || lower.contains("persistence")
678        || lower.contains("debugger hijack")
679        || lower.contains("ifeo")
680        || lower.contains("winlogon shell")
681        || lower.contains("bootexecute")
682        || lower.contains("reg query")
683        || lower.contains("regedit")
684        || lower.contains("sticky keys")
685        || lower.contains("sethc.exe");
686    let asks_share_access = lower.contains("share access")
687        || lower.contains("unc path")
688        || lower.contains("smbshare")
689        || lower.contains("net share")
690        || lower.contains("net use")
691        || lower.contains("\\\\")
692        || lower.contains("share is reachable")
693        || lower.contains("reachable share")
694        || (lower.contains("network share")
695            && (lower.contains("reach") || lower.contains("access") || lower.contains("test")));
696    let asks_thermal = lower.contains("thermal")
697        || (lower.contains("throttle") && !lower.contains("gpu"))
698        || lower.contains("overheating")
699        || lower.contains("cpu temp");
700    let asks_overclocker = lower.contains("overclocker")
701        || lower.contains("nvidia stats")
702        || lower.contains("silicon health")
703        || lower.contains("mhz")
704        || ((lower.contains("voltage") || lower.contains("volts"))
705            && (lower.contains("gpu")
706                || lower.contains("cpu")
707                || lower.contains("nvidia")
708                || lower.contains("silicon")))
709        || (lower.contains("gpu")
710            && (lower.contains("throttle")
711                || lower.contains("bottleneck")
712                || lower.contains("clock")
713                || lower.contains("fan")
714                || lower.contains("power draw")
715                || lower.contains("frequency")));
716    let asks_hardware = lower.contains("cpu model")
717        || lower.contains("ram size")
718        || lower.contains("hardware spec")
719        || (lower.contains("what hardware") && lower.contains("have"))
720        || (lower.contains("gpu") && (lower.contains("what") || lower.contains("show")))
721        || lower.contains("motherboard")
722        || lower.contains("bios version");
723    let asks_activation = lower.contains("activation")
724        || lower.contains("slmgr")
725        || lower.contains("license status")
726        || lower.contains("is windows genuine");
727    let asks_patch_history = lower.contains("patch history")
728        || lower.contains("hotfix")
729        || lower.contains("kb history")
730        || lower.contains("installed updates");
731    let asks_ports = lower.contains("listening on port")
732        || lower.contains("listening port")
733        || lower.contains("open port")
734        || lower.contains("port 3000")
735        || lower.contains("port ")
736        || lower.contains("listening on ")
737        || lower.contains("exposed")
738        || lower.contains("what is listening")
739        || (lower.contains("listening") && lower.contains("port"));
740    let asks_repo_doctor = lower.contains("repo doctor")
741        || lower.contains("repository doctor")
742        || lower.contains("workspace health")
743        || lower.contains("repo health")
744        || lower.contains("workspace sanity")
745        || (lower.contains("git state")
746            && (lower.contains("release artifacts")
747                || lower.contains("build markers")
748                || lower.contains("hematite memory")));
749    let asks_directory = lower.contains("directory")
750        || lower.contains("folder")
751        || lower.contains("how big")
752        || lower.contains("biggest");
753    let asks_mutation_intent = (lower.contains("make")
754        || lower.contains("create")
755        || lower.contains("mkdir")
756        || lower.contains("organize")
757        || lower.contains("edit")
758        || lower.contains("write")
759        || lower.contains("save")
760        || lower.contains("update"))
761        && (lower.contains("folder")
762            || lower.contains("directory")
763            || lower.contains("file")
764            || lower.contains("code")
765            || lower.contains("desktop"));
766    let asks_broad_readiness = lower.contains("local development")
767        || lower.contains("ready for local development")
768        || (lower.contains("machine") && lower.contains("ready"))
769        || (lower.contains("computer") && lower.contains("ready"));
770    let asks_os_config = lower.contains("firewall")
771        || lower.contains("power plan")
772        || lower.contains("power settings")
773        || lower.contains("powercfg")
774        || lower.contains("uptime")
775        || lower.contains("boot time")
776        || lower.contains("last boot");
777    let asks_health_report = lower.contains("health report")
778        || lower.contains("system health")
779        || (lower.contains("how") && lower.contains("machine") && lower.contains("doing"))
780        || (lower.contains("status") && lower.contains("report") && !lower.contains("git"));
781    let asks_updates = lower.contains("up to date")
782        || lower.contains("windows update")
783        || lower.contains("pending update")
784        || lower.contains("update available")
785        || lower.contains("check for update")
786        || lower.contains("latest update")
787        || (lower.contains("update")
788            && (lower.contains("my pc")
789                || lower.contains("my computer")
790                || lower.contains("my machine")));
791    let asks_security = lower.contains("antivirus")
792        || lower.contains("defender")
793        || lower.contains("virus protection")
794        || lower.contains("malware")
795        || lower.contains("windows security")
796        || lower.contains("uac")
797        || lower.contains("windows activated")
798        || lower.contains("activation status")
799        || (lower.contains("protected") && (lower.contains("pc") || lower.contains("computer")))
800        || (lower.contains("security")
801            && !lower.contains("git")
802            && !lower.contains("ssh")
803            && !lower.contains("token"));
804    let asks_pending_reboot = lower.contains("need to restart")
805        || lower.contains("need to reboot")
806        || lower.contains("requires restart")
807        || lower.contains("requires a reboot")
808        || lower.contains("reboot required")
809        || lower.contains("restart required")
810        || lower.contains("pending restart")
811        || lower.contains("pending reboot")
812        || (lower.contains("restart")
813            && (lower.contains("waiting")
814                || lower.contains("queued")
815                || lower.contains("required")))
816        || (lower.contains("reboot") && lower.contains("required"))
817        || (lower.contains("reboot") && lower.contains("pending"))
818        || (lower.contains("restart") && lower.contains("pending"));
819    let asks_disk_health = lower.contains("disk health")
820        || lower.contains("drive health")
821        || lower.contains("hard drive dying")
822        || lower.contains("smart status")
823        || lower.contains("drive failing")
824        || lower.contains("drive fail")
825        || (lower.contains("dying") && (lower.contains("drive") || lower.contains("disk")))
826        || (lower.contains("healthy")
827            && (lower.contains("drive")
828                || lower.contains("disk")
829                || lower.contains("ssd")
830                || lower.contains("hdd")));
831    let asks_battery = lower.contains("battery")
832        || lower.contains("battery life")
833        || lower.contains("battery health")
834        || lower.contains("battery wear")
835        || lower.contains("charge level")
836        || lower.contains("how long until")
837        || (lower.contains("dying") && lower.contains("batter"));
838    let asks_recent_crashes = lower.contains("crash")
839        || lower.contains("bsod")
840        || lower.contains("blue screen")
841        || lower.contains("why did my pc restart")
842        || lower.contains("unexpected restart")
843        || lower.contains("sudden restart")
844        || lower.contains("kernel panic")
845        || lower.contains("app crash")
846        || (lower.contains("restart") && lower.contains("itself"))
847        || (lower.contains("restart") && lower.contains("by itself"));
848    let asks_log_check = lower.contains("event log")
849        || lower.contains("windows log")
850        || lower.contains("system log")
851        || lower.contains("error log")
852        || lower.contains("recent errors")
853        || lower.contains("recent warnings")
854        || lower.contains("recent events")
855        || lower.contains("event viewer")
856        || lower.contains("journald")
857        || lower.contains("journal log")
858        || lower.contains("show me warnings")
859        || (lower.contains("log") && lower.contains("error"))
860        || (lower.contains("log") && lower.contains("warning"))
861        || (lower.contains("show me") && lower.contains("error"))
862        || (lower.contains("show me") && lower.contains("warning"))
863        || (lower.contains("what errors") && lower.contains("log"));
864    let asks_scheduled_tasks = lower.contains("scheduled task")
865        || lower.contains("scheduled tasks")
866        || lower.contains("task scheduler")
867        || lower.contains("what runs on a timer")
868        || lower.contains("what runs at")
869        || lower.contains("cron job")
870        || lower.contains("background task");
871    let asks_dev_conflicts = lower.contains("dev conflict")
872        || lower.contains("environment conflict")
873        || lower.contains("toolchain conflict")
874        || lower.contains("version conflict")
875        || lower.contains("path conflict")
876        || lower.contains("duplicate path")
877        || (lower.contains("python") && lower.contains("wrong version"))
878        || (lower.contains("node") && lower.contains("wrong version"))
879        || lower.contains("conda shadow")
880        || lower.contains("dev environment clean");
881    let asks_disk_benchmark = lower.contains("benchmark")
882        || lower.contains("stress test")
883        || lower.contains("load test")
884        || lower.contains("intensity report")
885        || lower.contains("io intensity")
886        || lower.contains("disk intensity")
887        || lower.contains("thrash")
888        || lower.contains("latency report");
889    let asks_storage = lower.contains("storage")
890        || lower.contains("disk space")
891        || lower.contains("drive capacity")
892        || lower.contains("free space")
893        || lower.contains("how much space")
894        || lower.contains("space left")
895        || lower.contains("running out of space")
896        || lower.contains("i/o pressure")
897        || lower.contains("disk usage")
898        || lower.contains("disk usage")
899        || lower.contains("how much disk")
900        || lower.contains("how full")
901        || lower.contains("cache size")
902        || (lower.contains("drive") && lower.contains("usage"))
903        || (lower.contains("drives") && lower.contains("usage"))
904        || (lower.contains("where") && lower.contains("space") && lower.contains("go"));
905    let asks_resource_load = lower.contains("resource load")
906        || lower.contains("system load")
907        || lower.contains("performance")
908        || lower.contains("utilization")
909        || lower.contains("usage report")
910        || lower.contains("performance report")
911        || lower.contains("what is my load")
912        || lower.contains("current load")
913        || lower.contains("why is it slow")
914        || lower.contains("why is it laggy")
915        || lower.contains("slow")
916        || lower.contains("lag")
917        || lower.contains("sluggish")
918        || lower.contains("hang")
919        || lower.contains("unresponsive")
920        || lower.contains("is it working hard")
921        || lower.contains("high cpu")
922        || lower.contains("high ram")
923        || lower.contains("cpu load")
924        || lower.contains("heavy hitters")
925        || (lower.contains("resource") && lower.contains("usage"));
926
927    let asks_connectivity = lower.contains("internet")
928        || lower.contains("online")
929        || lower.contains("connectivity")
930        || lower.contains("am i connected")
931        || lower.contains("ping google")
932        || lower.contains("reach the internet")
933        || lower.contains("internet access")
934        || lower.contains("no internet")
935        || lower.contains("internet down")
936        || lower.starts_with("ping ")
937        || lower.contains(" ping ")
938        || (lower.contains("check") && lower.contains("connection"))
939        || (lower.contains("dns") && (lower.contains("resolv") || lower.contains("working")));
940    let asks_wifi = lower.contains("wi-fi")
941        || lower.contains("wifi")
942        || lower.contains("wireless")
943        || lower.contains("wlan")
944        || lower.contains("signal strength")
945        || lower.contains("ssid")
946        || lower.contains("access point")
947        || (lower.contains("wireless") && lower.contains("connect"));
948    let asks_connections = lower.contains("tcp connection")
949        || lower.contains("active connection")
950        || lower.contains("established connection")
951        || lower.contains("socket")
952        || lower.contains("netstat")
953        || (lower.contains("connection") && lower.contains("active"))
954        || (lower.contains("connection") && lower.contains("open"));
955    let asks_vpn = lower.contains("vpn")
956        || lower.contains("virtual private network")
957        || (lower.contains("tunnel") && (lower.contains("network") || lower.contains("vpn")));
958    let asks_proxy = lower.contains("proxy")
959        || lower.contains("proxy setting")
960        || lower.contains("winhttp proxy")
961        || lower.contains("system proxy")
962        || (lower.contains("routed") && lower.contains("proxy"));
963    let asks_firewall_rules = (lower.contains("firewall")
964        && (lower.contains("rule")
965            || lower.contains("block")
966            || lower.contains("allow")
967            || lower.contains("inbound")
968            || lower.contains("outbound")))
969        || lower.contains("blocked port")
970        || lower.contains("firewall rule");
971    let asks_traceroute = lower.contains("traceroute")
972        || lower.contains("tracert")
973        || lower.contains("tracepath")
974        || lower.contains("trace route")
975        || lower.contains("trace the route")
976        || lower.contains("trace the path")
977        || lower.contains("network path")
978        || lower.contains("how many hops")
979        || lower.contains("where does traffic go")
980        || (lower.contains("trace") && lower.contains("hop"))
981        || (lower.contains("route") && lower.contains("traffic"))
982        || (lower.contains("trace") && lower.contains("8.8.8.8"))
983        || (lower.contains("path") && lower.contains("8.8.8.8"));
984    let asks_dns_cache = lower.contains("dns cache")
985        || lower.contains("cached dns")
986        || lower.contains("dns lookup cache")
987        || lower.contains("displaydns")
988        || lower.contains("/displaydns")
989        || lower.contains("get-dnsclientcache")
990        || lower.contains("dns entries")
991        || (lower.contains("dns") && lower.contains("cached"));
992    let asks_arp = lower.contains("arp -")
993        || lower.contains("arp table")
994        || lower.contains("arp cache")
995        || lower.contains("mac address")
996        || lower.contains("neighbor table")
997        || lower.contains("ip to mac")
998        || lower.contains("ip neigh")
999        || (lower.contains("arp")
1000            && (lower.contains("who") || lower.contains("entry") || lower.contains("entries")));
1001    let asks_route_table = lower.contains("route print")
1002        || lower.contains("route table")
1003        || lower.contains("routing table")
1004        || lower.contains("get-netroute")
1005        || lower.contains("default gateway")
1006        || lower.contains("network routes")
1007        || lower.contains("ip route")
1008        || lower.contains("next hop")
1009        || (lower.contains("route")
1010            && (lower.contains("table") || lower.contains("entry") || lower.contains("entries")));
1011    let asks_env = (lower.contains("environment variable")
1012        || lower.contains("env var")
1013        || lower.contains("env vars")
1014        || lower.contains("show env")
1015        || lower.contains("list env"))
1016        && !lower.contains("env doctor");
1017    let asks_hosts_file = lower.contains("hosts file")
1018        || lower.contains("/etc/hosts")
1019        || lower.contains("etc/hosts")
1020        || lower.contains("hosts entry")
1021        || lower.contains("hosts entries")
1022        || (lower.contains("hosts")
1023            && (lower.contains("redirect")
1024                || lower.contains("block")
1025                || lower.contains("loopback")));
1026    let asks_docker = lower.contains("docker")
1027        || lower.contains("container")
1028        || lower.contains("docker compose")
1029        || lower.contains("docker ps")
1030        || lower.contains("running container");
1031    let asks_docker_filesystems = (lower.contains("docker")
1032        || lower.contains("container")
1033        || lower.contains("compose")
1034        || lower.contains("volume")
1035        || lower.contains("bind mount"))
1036        && (lower.contains("mount")
1037            || lower.contains("volume")
1038            || lower.contains("bind")
1039            || lower.contains("filesystem")
1040            || lower.contains("storage")
1041            || lower.contains("path")
1042            || lower.contains("missing"));
1043    let asks_wsl = lower.contains("wsl")
1044        || lower.contains("windows subsystem")
1045        || lower.contains("linux distro")
1046        || lower.contains("ubuntu on windows")
1047        || (lower.contains("subsystem") && lower.contains("linux"));
1048    let asks_wsl_filesystems = (lower.contains("wsl")
1049        || lower.contains("windows subsystem")
1050        || lower.contains("linux distro")
1051        || lower.contains("ubuntu on windows")
1052        || (lower.contains("subsystem") && lower.contains("linux")))
1053        && (lower.contains("mount")
1054            || lower.contains("filesystem")
1055            || lower.contains("storage")
1056            || lower.contains("disk")
1057            || lower.contains("vhdx")
1058            || lower.contains("path bridge")
1059            || lower.contains("/mnt/c")
1060            || lower.contains("wsl df")
1061            || lower.contains("wsl du")
1062            || lower.contains("du -sh /mnt/c"));
1063    let asks_ssh = (lower.contains("ssh") && !lower.contains("ssh key") && !lower.contains("git"))
1064        || lower.contains("sshd")
1065        || lower.contains("ssh config")
1066        || lower.contains("ssh server")
1067        || lower.contains("ssh client")
1068        || lower.contains("known_hosts")
1069        || lower.contains("authorized_keys")
1070        || lower.contains("ssh key")
1071        || (lower.contains("ssh")
1072            && (lower.contains("running")
1073                || lower.contains("service")
1074                || lower.contains("port 22")));
1075    let asks_installed_software = lower.contains("installed software")
1076        || lower.contains("installed program")
1077        || lower.contains("installed app")
1078        || lower.contains("installed package")
1079        || lower.contains("what is installed")
1080        || lower.contains("what's installed")
1081        || lower.contains("winget list")
1082        || lower.contains("list programs")
1083        || (lower.contains("installed")
1084            && (lower.contains("on this machine")
1085                || lower.contains("on my machine")
1086                || lower.contains("on my pc")));
1087    let asks_databases = lower.contains("postgres")
1088        || lower.contains("postgresql")
1089        || lower.contains("mysql")
1090        || lower.contains("mariadb")
1091        || lower.contains("mongodb")
1092        || lower.contains("mongo")
1093        || lower.contains("redis")
1094        || lower.contains("sql server")
1095        || lower.contains("mssql")
1096        || lower.contains("sqlite")
1097        || lower.contains("elasticsearch")
1098        || lower.contains("cassandra")
1099        || lower.contains("couchdb")
1100        || (lower.contains("database")
1101            && (lower.contains("running")
1102                || lower.contains("service")
1103                || lower.contains("installed")
1104                || lower.contains("up")
1105                || lower.contains("local")))
1106        || lower.contains("db service")
1107        || lower.contains("database server")
1108        || (lower.contains("is")
1109            && lower.contains("running")
1110            && (lower.contains("db") || lower.contains("database")));
1111    let asks_git_config = (lower.contains("git config")
1112        || lower.contains("git configuration")
1113        || lower.contains("git global")
1114        || (lower.contains("git") && lower.contains("user.name"))
1115        || (lower.contains("git") && lower.contains("user.email"))
1116        || (lower.contains("git") && lower.contains("signing"))
1117        || (lower.contains("git") && lower.contains("credential"))
1118        || lower.contains("git aliases"))
1119        && !lower.contains("github");
1120    let asks_audit_policy = lower.contains("audit policy")
1121        || lower.contains("auditpol")
1122        || lower.contains("audit log")
1123        || lower.contains("what is being logged")
1124        || lower.contains("security audit")
1125        || lower.contains("logon event")
1126        || lower.contains("audit category")
1127        || lower.contains("event auditing");
1128    let asks_shares = lower.contains("smb share")
1129        || lower.contains("network share")
1130        || lower.contains("shared folder")
1131        || lower.contains("mapped drive")
1132        || lower.contains("mapped network drive")
1133        || lower.contains("get-smbshare")
1134        || lower.contains("what is shared")
1135        || lower.contains("what am i sharing")
1136        || lower.contains("smb session")
1137        || lower.contains("lanmanager")
1138        || lower.contains("netlanmanager")
1139        || lower.contains("smb1")
1140        || lower.contains("smb signing")
1141        || lower.contains("nfs export");
1142    let asks_dns_servers = (lower.contains("dns server")
1143        || lower.contains("dns resolver")
1144        || lower.contains("nameserver")
1145        || lower.contains("which dns")
1146        || lower.contains("what dns")
1147        || lower.contains("dns over https")
1148        || lower.contains("doh")
1149        || lower.contains("dns search suffix")
1150        || lower.contains("configured dns")
1151        || lower.contains("get-dnsclientserveraddress"))
1152        && !lower.contains("dns cache");
1153    let asks_bitlocker = lower.contains("bitlocker")
1154        || (lower.contains("drive") && lower.contains("encrypt"))
1155        || (lower.contains("disk") && lower.contains("encrypt"))
1156        || lower.contains("encryption status");
1157    let asks_rdp = lower.contains("rdp")
1158        || lower.contains("remote desktop")
1159        || (lower.contains("remote") && lower.contains("access") && !lower.contains("git"));
1160    let asks_shadow_copies = lower.contains("shadow copy")
1161        || lower.contains("shadow copies")
1162        || lower.contains("vss")
1163        || lower.contains("snapshot")
1164        || lower.contains("restore point");
1165    let asks_pagefile = lower.contains("pagefile")
1166        || lower.contains("page file")
1167        || lower.contains("virtual memory")
1168        || lower.contains("swap file")
1169        || (lower.contains("paging") && lower.contains("file"));
1170    let asks_windows_features = (lower.contains("window") && lower.contains("feature"))
1171        || lower.contains("optional feature")
1172        || lower.contains("iis")
1173        || lower.contains("hyper-v")
1174        || (lower.contains("feature")
1175            && (lower.contains("install")
1176                || lower.contains("enabled")
1177                || lower.contains("turn on")));
1178    let asks_printers =
1179        lower.contains("printer") || lower.contains("print queue") || lower.contains("get-printer");
1180    let asks_winrm = lower.contains("winrm")
1181        || lower.contains("psremoting")
1182        || (lower.contains("ps") && lower.contains("remoting"))
1183        || (lower.contains("remote") && lower.contains("management") && !lower.contains("rdp"));
1184    let asks_network_stats = (lower.contains("network") && lower.contains("stat"))
1185        || (lower.contains("adapter") && lower.contains("stat"))
1186        || (lower.contains("nic") && lower.contains("stat"))
1187        || lower.contains("throughput")
1188        || lower.contains("packet loss")
1189        || lower.contains("dropped packet");
1190    let asks_udp_ports = lower.contains("udp port")
1191        || lower.contains("udp listener")
1192        || (lower.contains("udp")
1193            && (lower.contains("port") || lower.contains("listen") || lower.contains("open")));
1194
1195    // If the user has a clear mutation intent (create folder, edit file),
1196    // we should NOT route to a read-only host inspection topic, as that would
1197    // trigger a pre-run crash. The main LLM turn will handle the mutation.
1198    if asks_mutation_intent {
1199        return None;
1200    }
1201
1202    // Priority 1: High-Precision Enterprise Triage (IT Pro Plus)
1203    if asks_overclocker {
1204        Some("overclocker")
1205    } else if asks_ad_user {
1206        Some("ad_user")
1207    } else if asks_user_accounts {
1208        Some("user_accounts")
1209    } else if asks_dns_lookup {
1210        Some("dns_lookup")
1211    } else if asks_hyperv {
1212        Some("hyperv")
1213    } else if asks_ip_config {
1214        Some("ip_config")
1215    } else if asks_disk_benchmark {
1216        Some("disk_benchmark")
1217    } else if asks_fix_plan {
1218        Some("fix_plan")
1219    } else if asks_env_doctor {
1220        Some("env_doctor")
1221    } else if asks_overclocker {
1222        Some("overclocker")
1223    } else if asks_network_stats {
1224        Some("network_stats")
1225    } else if asks_share_access {
1226        Some("share_access")
1227    } else if asks_thermal {
1228        Some("thermal")
1229    } else if asks_activation {
1230        Some("activation")
1231    } else if asks_patch_history {
1232        Some("patch_history")
1233    } else if asks_bluetooth {
1234        Some("bluetooth")
1235    } else if asks_audio {
1236        Some("audio")
1237    } else if asks_camera {
1238        Some("camera")
1239    } else if asks_sign_in {
1240        Some("sign_in")
1241    } else if asks_search_index {
1242        Some("search_index")
1243    } else if asks_docker_filesystems {
1244        Some("docker_filesystems")
1245    } else if asks_wsl_filesystems {
1246        Some("wsl_filesystems")
1247    } else if asks_lan_discovery {
1248        Some("lan_discovery")
1249    } else if asks_storage {
1250        Some("storage")
1251    } else if asks_gpo {
1252        Some("gpo")
1253    } else if asks_certificates {
1254        Some("certificates")
1255    } else if asks_integrity {
1256        Some("integrity")
1257    } else if asks_domain {
1258        Some("domain")
1259    } else if asks_device_health {
1260        Some("device_health")
1261    } else if asks_drivers {
1262        Some("drivers")
1263    } else if asks_peripherals {
1264        Some("peripherals")
1265    } else if asks_user_accounts {
1266        Some("user_accounts")
1267    } else if asks_sessions {
1268        Some("sessions")
1269    } else if asks_virtualization {
1270        Some("hardware")
1271    } else if asks_services {
1272        Some("services")
1273    } else if asks_startup {
1274        Some("startup_items")
1275    } else if asks_bitlocker {
1276        Some("bitlocker")
1277    } else if asks_rdp {
1278        Some("rdp")
1279    } else if asks_shadow_copies {
1280        Some("shadow_copies")
1281    } else if asks_pagefile {
1282        Some("pagefile")
1283    } else if asks_windows_features {
1284        Some("windows_features")
1285    } else if asks_printers {
1286        Some("printers")
1287    } else if asks_winrm {
1288        Some("winrm")
1289    } else if (asks_path && asks_toolchains)
1290        || (mentions_host_inspection_question(&lower) && asks_broad_readiness)
1291    {
1292        Some("summary")
1293    } else if asks_env_doctor {
1294        Some("env_doctor")
1295    } else if asks_dns_servers {
1296        Some("dns_servers")
1297    } else if asks_lan_discovery {
1298        Some("lan_discovery")
1299    } else if asks_connectivity {
1300        Some("connectivity")
1301    } else if asks_wifi {
1302        Some("wifi")
1303    } else if asks_connections {
1304        Some("connections")
1305    } else if asks_vpn {
1306        Some("vpn")
1307    } else if asks_proxy {
1308        Some("proxy")
1309    } else if asks_firewall_rules {
1310        Some("firewall_rules")
1311    } else if asks_traceroute {
1312        Some("traceroute")
1313    } else if asks_dns_cache {
1314        Some("dns_cache")
1315    } else if asks_arp {
1316        Some("arp")
1317    } else if asks_route_table {
1318        Some("route_table")
1319    } else if asks_network_stats {
1320        Some("network_stats")
1321    } else if asks_udp_ports {
1322        Some("udp_ports")
1323    } else if asks_shares {
1324        Some("shares")
1325    } else if asks_health_report {
1326        Some("health_report")
1327    } else if asks_os_config {
1328        Some("os_config")
1329    } else if asks_hardware || asks_virtualization {
1330        Some("hardware")
1331    } else if asks_network {
1332        Some("network")
1333    } else if asks_updates {
1334        Some("updates")
1335    } else if asks_audit_policy {
1336        Some("audit_policy")
1337    } else if asks_security {
1338        Some("security")
1339    } else if asks_pending_reboot {
1340        Some("pending_reboot")
1341    } else if asks_disk_health {
1342        Some("disk_health")
1343    } else if asks_battery {
1344        Some("battery")
1345    } else if asks_recent_crashes {
1346        Some("recent_crashes")
1347    } else if asks_log_check {
1348        Some("log_check")
1349    } else if asks_scheduled_tasks {
1350        Some("scheduled_tasks")
1351    } else if asks_dev_conflicts {
1352        Some("dev_conflicts")
1353    } else if asks_databases {
1354        Some("databases")
1355    } else if asks_docker {
1356        Some("docker")
1357    } else if asks_wsl {
1358        Some("wsl")
1359    } else if asks_ssh {
1360        Some("ssh")
1361    } else if asks_git_config {
1362        Some("git_config")
1363    } else if asks_installed_software {
1364        Some("installed_software")
1365    } else if asks_env {
1366        Some("env")
1367    } else if asks_hosts_file {
1368        Some("hosts_file")
1369    } else if asks_ports {
1370        Some("ports")
1371    } else if asks_processes {
1372        Some("processes")
1373    } else if asks_repo_doctor {
1374        Some("repo_doctor")
1375    } else if lower.contains("desktop") {
1376        Some("desktop")
1377    } else if lower.contains("downloads") {
1378        Some("downloads")
1379    } else if asks_path {
1380        Some("path")
1381    } else if asks_toolchains {
1382        Some("toolchains")
1383    } else if asks_resource_load {
1384        Some("resource_load")
1385    } else if asks_directory {
1386        Some("directory")
1387    } else if mentions_host_inspection_question(&lower) {
1388        Some("summary")
1389    } else {
1390        None
1391    }
1392}
1393
1394pub fn all_host_inspection_topics(user_input: &str) -> Vec<&'static str> {
1395    // All topic detectors in priority order — ordered so more specific topics come
1396    // before generic fallbacks (e.g. traceroute before network).
1397    let lower = user_input.to_lowercase();
1398    let mut topics: Vec<&'static str> = Vec::new();
1399
1400    let detectors: &[(&str, fn(&str) -> bool)] = &[
1401        ("overclocker", |l| {
1402            l.contains("overclocker")
1403                || l.contains("gpu clock")
1404                || l.contains("gpu throttle")
1405                || l.contains("throttle reason")
1406                || l.contains("root cause")
1407                || l.contains("nvidia stats")
1408                || l.contains("silicon health")
1409                || ((l.contains("voltage") || l.contains("volts"))
1410                    && (l.contains("gpu")
1411                        || l.contains("cpu")
1412                        || l.contains("nvidia")
1413                        || l.contains("silicon")))
1414                || (l.contains("gpu")
1415                    && (l.contains("throttle")
1416                        || l.contains("bottleneck")
1417                        || l.contains("performance")))
1418        }),
1419        ("directory", |l| {
1420            (l.contains("make")
1421                || l.contains("create")
1422                || l.contains("mkdir")
1423                || l.contains("organize"))
1424                && (l.contains("folder")
1425                    || l.contains("directory")
1426                    || l.contains("project area")
1427                    || l.contains("desktop"))
1428        }),
1429        ("ad_user", |l| {
1430            l.contains("ad user")
1431                || l.contains("domain user")
1432                || (l.contains("user") && (l.contains("sid") || l.contains("membership")))
1433        }),
1434        ("dns_lookup", |l| {
1435            l.contains("dns lookup")
1436                || l.contains("dns record")
1437                || l.contains("dns query")
1438                || l.contains("nslookup")
1439                || l.contains(" dig ")
1440                || l.contains("srv record")
1441                || l.contains("mx record")
1442        }),
1443        ("hyperv", |l| {
1444            l.contains("hyper-v")
1445                || l.contains("hyperv")
1446                || (l.contains("virtual machine") && l.contains("load"))
1447                || l.contains("vmmem")
1448        }),
1449        ("ip_config", |l| {
1450            l.contains("ipconfig")
1451                || l.contains("ip config")
1452                || l.contains("adapter detail")
1453                || l.contains("dhcp lease")
1454        }),
1455        ("fix_plan", |l| {
1456            l.contains("fix")
1457                && (l.contains("cargo")
1458                    || l.contains("port ")
1459                    || l.contains("lm studio")
1460                    || l.contains("toolchain"))
1461        }),
1462        ("updates", |l| {
1463            l.contains("up to date")
1464                || l.contains("windows update")
1465                || l.contains("pending update")
1466                || l.contains("update available")
1467        }),
1468        ("security", |l| {
1469            l.contains("antivirus")
1470                || l.contains("defender")
1471                || l.contains("uac")
1472                || (l.contains("security") && !l.contains("git") && !l.contains("ssh"))
1473        }),
1474        ("permissions", |l| {
1475            l.contains("permission") || l.contains("access control") || l.contains("get-acl")
1476        }),
1477        ("login_history", |l| {
1478            l.contains("login history")
1479                || l.contains("logon history")
1480                || l.contains("event id 4624")
1481        }),
1482        ("registry_audit", |l| {
1483            l.contains("registry audit")
1484                || l.contains("persistence")
1485                || l.contains("ifeo")
1486                || l.contains("reg query")
1487        }),
1488        ("share_access", |l| {
1489            l.contains("share access")
1490                || l.contains("unc path")
1491                || l.contains("smbshare")
1492                || l.contains("net share")
1493        }),
1494        ("thermal", |l| {
1495            l.contains("thermal") || l.contains("throttling") || l.contains("overheating")
1496        }),
1497        ("overclocker", |l| {
1498            l.contains("overclocker")
1499                || l.contains("gpu clock")
1500                || l.contains("nvidia stats")
1501                || l.contains("silicon health")
1502                || l.contains("mhz")
1503        }),
1504        ("activation", |l| {
1505            l.contains("activation") || l.contains("slmgr") || l.contains("license status")
1506        }),
1507        ("patch_history", |l| {
1508            l.contains("patch history") || l.contains("hotfix") || l.contains("kb history")
1509        }),
1510        ("bluetooth", |l| {
1511            l.contains("bluetooth")
1512                || l.contains("pairing")
1513                || l.contains("paired device")
1514                || l.contains("paired devices")
1515                || l.contains("bthserv")
1516                || l.contains("bthavctpsvc")
1517                || l.contains("btagservice")
1518                || l.contains("bluetoothuserservice")
1519                || ((l.contains("headset") || l.contains("headphones"))
1520                    && (l.contains("disconnect")
1521                        || l.contains("pair")
1522                        || l.contains("reconnect")
1523                        || l.contains("bluetooth")))
1524        }),
1525        ("audio", |l| {
1526            l.contains("no sound")
1527                || l.contains("audio service")
1528                || l.contains("windows audio")
1529                || l.contains("speaker")
1530                || l.contains("speakers")
1531                || l.contains("microphone")
1532                || l.contains(" mic ")
1533                || l.starts_with("mic ")
1534                || l.contains("mic not")
1535                || l.contains("headset")
1536                || l.contains("headphones")
1537                || l.contains("playback device")
1538                || l.contains("recording device")
1539                || l.contains("audio endpoint")
1540                || l.contains("audioendpointbuilder")
1541                || (((l.contains("audio") || l.contains("sound"))
1542                    && (l.contains("device")
1543                        || l.contains("driver")
1544                        || l.contains("service")
1545                        || l.contains("working")
1546                        || l.contains("broken")
1547                        || l.contains("input")
1548                        || l.contains("output")
1549                        || l.contains("crackling")
1550                        || l.contains("mute")
1551                        || l.contains("muted")
1552                        || l.contains("volume")
1553                        || l.contains("speaker")
1554                        || l.contains("microphone")))
1555                    && !l.contains("audio file")
1556                    && !l.contains("voice engine"))
1557        }),
1558        ("camera", |l| {
1559            l.contains("camera")
1560                || l.contains("webcam")
1561                || l.contains("web cam")
1562                || (l.contains("camera") && l.contains("permission"))
1563                || (l.contains("camera") && l.contains("privacy"))
1564        }),
1565        ("sign_in", |l| {
1566            l.contains("windows hello")
1567                || l.contains("sign in")
1568                || l.contains("cant sign in")
1569                || l.contains("can't sign in")
1570                || (l.contains("pin") && (l.contains("broken") || l.contains("not working")))
1571                || l.contains("credential provider")
1572                || l.contains("biometric service")
1573                || l.contains("wbiosrvc")
1574        }),
1575        ("search_index", |l| {
1576            l.contains("search index")
1577                || l.contains("windows search")
1578                || l.contains("wsearch")
1579                || l.contains("indexer")
1580                || (l.contains("search") && l.contains("broken"))
1581                || (l.contains("search") && l.contains("not working"))
1582        }),
1583        ("pending_reboot", |l| {
1584            l.contains("pending reboot")
1585                || l.contains("pending restart")
1586                || l.contains("need to restart")
1587                || l.contains("reboot required")
1588                || (l.contains("reboot") && l.contains("pending"))
1589                || (l.contains("restart") && l.contains("pending"))
1590        }),
1591        ("disk_health", |l| {
1592            l.contains("disk health")
1593                || l.contains("drive health")
1594                || l.contains("smart status")
1595                || (l.contains("healthy")
1596                    && (l.contains("drive") || l.contains("disk") || l.contains("ssd")))
1597        }),
1598        ("battery", |l| l.contains("battery")),
1599        ("recent_crashes", |l| {
1600            l.contains("crash") || l.contains("bsod") || l.contains("blue screen")
1601        }),
1602        ("scheduled_tasks", |l| {
1603            l.contains("scheduled task") || l.contains("task scheduler")
1604        }),
1605        ("ad_user", |l| {
1606            l.contains("ad user")
1607                || l.contains("domain user")
1608                || (l.contains("user") && l.contains("sid"))
1609        }),
1610        ("dns_lookup", |l| {
1611            l.contains("dns") && (l.contains("lookup") || l.contains("srv") || l.contains("mx"))
1612        }),
1613        ("hyperv", |l| {
1614            l.contains("hyper-v")
1615                || l.contains("hyperv")
1616                || (l.contains("list") && l.contains("vm"))
1617        }),
1618        ("ip_config", |l| {
1619            l.contains("ipconfig") && (l.contains("all") || l.contains("detail"))
1620        }),
1621        ("dev_conflicts", |l| {
1622            l.contains("dev conflict")
1623                || l.contains("toolchain conflict")
1624                || l.contains("duplicate path")
1625        }),
1626        ("storage", |l| {
1627            l.contains("disk space")
1628                || l.contains("storage")
1629                || l.contains("drive capacity")
1630                || l.contains("cache size")
1631                || l.contains("i/o pressure")
1632                || l.contains("disk usage")
1633        }),
1634        ("hardware", |l| {
1635            l.contains("cpu model")
1636                || l.contains("ram size")
1637                || l.contains("hardware spec")
1638                || (l.contains("what hardware") && l.contains("have"))
1639        }),
1640        ("health_report", |l| {
1641            l.contains("health report") || l.contains("system health")
1642        }),
1643        ("resource_load", |l| {
1644            l.contains("resource load")
1645                || l.contains("cpu load")
1646                || l.contains("ram %")
1647                || l.contains("cpu %")
1648                || l.contains("performance")
1649                || l.contains("slow")
1650                || l.contains("lag")
1651                || l.contains("sluggish")
1652                || l.contains("hang")
1653                || l.contains("unresponsive")
1654        }),
1655        ("processes", |l| {
1656            l.contains("process")
1657                || l.contains("task manager")
1658                || l.contains("what is running")
1659                || l.contains("using my ram")
1660                || l.contains("hitting the disk")
1661                || l.contains("disk thrasher")
1662        }),
1663        ("services", |l| {
1664            l.contains("service") || l.contains("daemon") || l.contains("windows service")
1665        }),
1666        ("ports", |l| {
1667            l.contains("listening port")
1668                || l.contains("open port")
1669                || l.contains("what is on port")
1670                || l.contains("port 3000")
1671                || (l.contains("listening") && l.contains("port"))
1672        }),
1673        ("traceroute", |l| {
1674            l.contains("traceroute")
1675                || l.contains("tracert")
1676                || l.contains("trace route")
1677                || l.contains("trace the path")
1678                || l.contains("network path")
1679                || l.contains("how many hops")
1680                || (l.contains("trace") && l.contains("hop"))
1681        }),
1682        ("dns_cache", |l| {
1683            l.contains("dns cache")
1684                || l.contains("cached dns")
1685                || l.contains("displaydns")
1686                || (l.contains("dns") && l.contains("cached"))
1687        }),
1688        ("arp", |l| {
1689            l.contains("arp table")
1690                || l.contains("arp cache")
1691                || l.contains("mac address")
1692                || l.contains("ip to mac")
1693                || l.contains("arp -")
1694        }),
1695        ("route_table", |l| {
1696            l.contains("route table")
1697                || l.contains("routing table")
1698                || l.contains("route print")
1699                || l.contains("network route")
1700                || l.contains("next hop")
1701        }),
1702        ("connectivity", |l| {
1703            l.contains("internet")
1704                || l.contains("am i connected")
1705                || l.contains("ping google")
1706                || l.contains("internet access")
1707                || l.contains("no internet")
1708        }),
1709        ("wifi", |l| {
1710            l.contains("wi-fi")
1711                || l.contains("wifi")
1712                || l.contains("wireless")
1713                || l.contains("ssid")
1714                || l.contains("signal strength")
1715        }),
1716        ("connections", |l| {
1717            l.contains("tcp connection")
1718                || l.contains("active connection")
1719                || l.contains("netstat")
1720                || l.contains("open socket")
1721                || (l.contains("established") && l.contains("connection"))
1722        }),
1723        ("vpn", |l| {
1724            l.contains("vpn") || l.contains("virtual private network")
1725        }),
1726        ("proxy", |l| {
1727            l.contains("proxy setting") || l.contains("system proxy") || l.contains("winhttp proxy")
1728        }),
1729        ("firewall_rules", |l| {
1730            (l.contains("firewall")
1731                && (l.contains("rule") || l.contains("inbound") || l.contains("outbound")))
1732                || l.contains("firewall rule")
1733        }),
1734        ("lan_discovery", |l| {
1735            l.contains("upnp")
1736                || l.contains("ssdp")
1737                || l.contains("mdns")
1738                || l.contains("bonjour")
1739                || l.contains("llmnr")
1740                || l.contains("network neighborhood")
1741                || l.contains("device discovery")
1742                || l.contains("local discovery")
1743                || l.contains("discover local devices")
1744                || l.contains("discover devices")
1745                || l.contains("browse computers")
1746                || (l.contains("local network")
1747                    && (l.contains("discover")
1748                        || l.contains("discovery")
1749                        || l.contains("neighborhood")
1750                        || l.contains("device")
1751                        || l.contains("devices")
1752                        || l.contains("aware of")))
1753                || ((l.contains("netbios") || l.contains("smb visibility"))
1754                    && !l.contains("active directory"))
1755                || ((l.contains("nas")
1756                    || l.contains("printer")
1757                    || l.contains("device")
1758                    || l.contains("computer")
1759                    || l.contains("pc"))
1760                    && ((l.contains("can't") && l.contains("see"))
1761                        || (l.contains("cannot") && l.contains("see"))
1762                        || (l.contains("cant") && l.contains("see"))
1763                        || l.contains("can't see")
1764                        || l.contains("cannot see")
1765                        || l.contains("cant see")
1766                        || l.contains("not visible")
1767                        || l.contains("not showing up")
1768                        || l.contains("not show up")
1769                        || l.contains("discover"))
1770                    && (l.contains("network")
1771                        || l.contains("lan")
1772                        || l.contains("local")
1773                        || l.contains("neighborhood")))
1774        }),
1775        ("network", |l| {
1776            l.contains("network adapter")
1777                || l.contains("ip address")
1778                || l.contains("ipconfig")
1779                || l.contains("gateway")
1780                || l.contains("subnet")
1781        }),
1782        ("env_doctor", |l| {
1783            l.contains("env doctor")
1784                || l.contains("environment doctor")
1785                || l.contains("package manager")
1786                || l.contains("path drift")
1787        }),
1788        ("os_config", |l| {
1789            l.contains("power plan")
1790                || l.contains("uptime")
1791                || l.contains("boot time")
1792                || l.contains("last boot")
1793        }),
1794        ("overclocker", |l| {
1795            l.contains("overclocker")
1796                || l.contains("gpu clock")
1797                || l.contains("gpu throttle")
1798                || l.contains("nvidia stats")
1799                || l.contains("silicon health")
1800                || l.contains("mhz")
1801                || ((l.contains("voltage") || l.contains("volts"))
1802                    && (l.contains("gpu")
1803                        || l.contains("cpu")
1804                        || l.contains("nvidia")
1805                        || l.contains("silicon")))
1806                || (l.contains("gpu") && (l.contains("throttle") || l.contains("bottleneck")))
1807        }),
1808        ("path", |l| {
1809            l.contains("path entries") || l.contains("raw path")
1810        }),
1811        ("toolchains", |l| {
1812            l.contains("developer tools")
1813                || l.contains("toolchains")
1814                || (l.contains("installed") && l.contains("version"))
1815        }),
1816        ("docker", |l| {
1817            l.contains("docker") || l.contains("container") || l.contains("running container")
1818        }),
1819        ("docker_filesystems", |l| {
1820            (l.contains("docker")
1821                || l.contains("container")
1822                || l.contains("compose")
1823                || l.contains("volume")
1824                || l.contains("bind mount"))
1825                && (l.contains("mount")
1826                    || l.contains("volume")
1827                    || l.contains("bind")
1828                    || l.contains("filesystem")
1829                    || l.contains("storage")
1830                    || l.contains("path")
1831                    || l.contains("missing"))
1832        }),
1833        ("wsl", |l| {
1834            l.contains("wsl")
1835                || l.contains("windows subsystem")
1836                || (l.contains("subsystem") && l.contains("linux"))
1837        }),
1838        ("wsl_filesystems", |l| {
1839            (l.contains("wsl")
1840                || l.contains("windows subsystem")
1841                || l.contains("linux distro")
1842                || (l.contains("subsystem") && l.contains("linux")))
1843                && (l.contains("mount")
1844                    || l.contains("filesystem")
1845                    || l.contains("storage")
1846                    || l.contains("disk")
1847                    || l.contains("vhdx")
1848                    || l.contains("path bridge")
1849                    || l.contains("/mnt/c")
1850                    || l.contains("wsl df")
1851                    || l.contains("wsl du")
1852                    || l.contains("du -sh /mnt/c"))
1853        }),
1854        ("ssh", |l| {
1855            l.contains("ssh")
1856                || l.contains("sshd")
1857                || l.contains("known_hosts")
1858                || l.contains("authorized_keys")
1859        }),
1860        ("git_config", |l| {
1861            (l.contains("git config") || l.contains("git global") || l.contains("git aliases"))
1862                && !l.contains("github")
1863        }),
1864        ("installed_software", |l| {
1865            l.contains("installed software")
1866                || l.contains("installed program")
1867                || l.contains("what is installed")
1868                || l.contains("what's installed")
1869                || l.contains("winget list")
1870        }),
1871        ("env", |l| {
1872            (l.contains("environment variable") || l.contains("env var") || l.contains("env vars"))
1873                && !l.contains("env doctor")
1874        }),
1875        ("hosts_file", |l| {
1876            l.contains("hosts file") || l.contains("/etc/hosts") || l.contains("hosts entry")
1877        }),
1878        ("databases", |l| {
1879            l.contains("postgres")
1880                || l.contains("mysql")
1881                || l.contains("mariadb")
1882                || l.contains("mongodb")
1883                || l.contains("redis")
1884                || l.contains("sqlite")
1885                || l.contains("sql server")
1886                || l.contains("elasticsearch")
1887                || (l.contains("database") && (l.contains("running") || l.contains("service")))
1888        }),
1889        ("user_accounts", |l| {
1890            l.contains("local user")
1891                || l.contains("user account")
1892                || l.contains("who is logged")
1893                || l.contains("who am i")
1894                || l.contains("logged in as")
1895                || l.contains("admin group")
1896                || l.contains("local admin")
1897                || l.contains("active sessions")
1898                || l.contains("running as admin")
1899        }),
1900        ("audit_policy", |l| {
1901            l.contains("audit policy")
1902                || l.contains("auditpol")
1903                || l.contains("what is being logged")
1904                || l.contains("security audit")
1905                || l.contains("event auditing")
1906        }),
1907        ("shares", |l| {
1908            l.contains("smb share")
1909                || l.contains("network share")
1910                || l.contains("shared folder")
1911                || l.contains("mapped drive")
1912                || l.contains("smb1")
1913                || l.contains("nfs export")
1914        }),
1915        ("dns_servers", |l| {
1916            (l.contains("dns server")
1917                || l.contains("dns resolver")
1918                || l.contains("nameserver")
1919                || l.contains("which dns")
1920                || l.contains("dns over https")
1921                || l.contains("configured dns"))
1922                && !l.contains("dns cache")
1923        }),
1924        ("bitlocker", |l| {
1925            l.contains("bitlocker")
1926                || (l.contains("drive") && l.contains("encrypt"))
1927                || (l.contains("disk") && l.contains("encrypt"))
1928                || l.contains("encryption status")
1929        }),
1930        ("rdp", |l| {
1931            l.contains("rdp")
1932                || l.contains("remote desktop")
1933                || (l.contains("remote") && l.contains("access") && !l.contains("git"))
1934        }),
1935        ("shadow_copies", |l| {
1936            l.contains("shadow copy")
1937                || l.contains("shadow copies")
1938                || l.contains("vss")
1939                || l.contains("snapshot")
1940                || l.contains("restore point")
1941        }),
1942        ("pagefile", |l| {
1943            l.contains("pagefile")
1944                || l.contains("page file")
1945                || l.contains("virtual memory")
1946                || l.contains("swap file")
1947        }),
1948        ("windows_features", |l| {
1949            (l.contains("window") && l.contains("feature"))
1950                || l.contains("optional feature")
1951                || l.contains("iis")
1952                || l.contains("hyper-v")
1953                || (l.contains("feature") && (l.contains("install") || l.contains("enabled")))
1954        }),
1955        ("printers", |l| {
1956            l.contains("printer") || l.contains("print queue") || l.contains("get-printer")
1957        }),
1958        ("winrm", |l| {
1959            l.contains("winrm")
1960                || l.contains("psremoting")
1961                || (l.contains("remote") && l.contains("management") && !l.contains("rdp"))
1962        }),
1963        ("network_stats", |l| {
1964            (l.contains("network") && l.contains("stat"))
1965                || (l.contains("adapter") && l.contains("stat"))
1966                || l.contains("throughput")
1967                || l.contains("packet loss")
1968                || l.contains("dropped packet")
1969        }),
1970        ("startup_items", |l| {
1971            l.contains("startup") || l.contains("boot program") || l.contains("autorun")
1972        }),
1973        ("udp_ports", |l| {
1974            l.contains("udp port")
1975                || l.contains("udp listener")
1976                || (l.contains("udp") && l.contains("listening"))
1977        }),
1978        ("gpo", |l| {
1979            l.contains("gpo") || l.contains("group policy") || l.contains("gpresult")
1980        }),
1981        ("certificates", |l| {
1982            l.contains("cert") || l.contains("ssl") || l.contains("thumbprint")
1983        }),
1984        ("integrity", |l| {
1985            l.contains("integrity") || l.contains("sfc") || l.contains("dism")
1986        }),
1987        ("domain", |l| {
1988            l.contains("domain") || l.contains("workgroup") || l.contains("active directory")
1989        }),
1990        ("device_health", |l| {
1991            l.contains("device health")
1992                || l.contains("hardware error")
1993                || l.contains("yellow bang")
1994                || l.contains("malfunctioning")
1995        }),
1996        ("drivers", |l| {
1997            l.contains("driver") || l.contains("system driver")
1998        }),
1999        ("peripherals", |l| {
2000            l.contains("peripheral")
2001                || l.contains("usb")
2002                || l.contains("keyboard")
2003                || l.contains("mouse")
2004                || l.contains("monitor")
2005        }),
2006        ("sessions", |l| {
2007            l.contains("session") || l.contains("who is logged") || l.contains("active login")
2008        }),
2009        ("hardware", |l| {
2010            l.contains("virtualization")
2011                || l.contains("hypervisor")
2012                || l.contains("vt-x")
2013                || l.contains("slat")
2014        }),
2015    ];
2016
2017    for (topic, check) in detectors {
2018        if check(&lower) && !topics.contains(topic) {
2019            topics.push(topic);
2020        }
2021    }
2022
2023    if topics.contains(&"docker_filesystems") {
2024        topics.retain(|topic| *topic != "docker");
2025        topics.retain(|topic| *topic != "storage");
2026    }
2027    if topics.contains(&"wsl_filesystems") {
2028        topics.retain(|topic| *topic != "wsl");
2029        topics.retain(|topic| *topic != "storage");
2030    }
2031    if topics.contains(&"lan_discovery") {
2032        topics.retain(|topic| *topic != "network");
2033    }
2034    if topics.contains(&"audio") {
2035        topics.retain(|topic| *topic != "peripherals");
2036    }
2037    if topics.contains(&"bluetooth") {
2038        topics.retain(|topic| *topic != "peripherals");
2039    }
2040
2041    topics
2042}
2043
2044pub(crate) fn preferred_maintainer_workflow(user_input: &str) -> Option<&'static str> {
2045    let lower = user_input.to_ascii_lowercase();
2046    let asks_cleanup = contains_any(
2047        &lower,
2048        &[
2049            "run my cleanup",
2050            "run the cleanup",
2051            "run cleanup",
2052            "deep clean",
2053            "prune dist",
2054            "clean.ps1",
2055            "cleanup script",
2056            "cleanup workflow",
2057            "clean up scripts",
2058        ],
2059    );
2060    let asks_package = contains_any(
2061        &lower,
2062        &[
2063            "rebuild local portable",
2064            "rebuild the portable",
2065            "run the local build",
2066            "run the portable",
2067            "package-windows.ps1",
2068            "package windows",
2069            "build installer",
2070            "overwrite the portable",
2071            "refresh the portable",
2072            "update path",
2073            "update path with the portable",
2074        ],
2075    );
2076    let asks_release = contains_any(
2077        &lower,
2078        &[
2079            "run the release flow",
2080            "regular workflow",
2081            "cut the release",
2082            "ship it",
2083            "release.ps1",
2084            "bump to ",
2085            "tag it",
2086            "full tag and everything",
2087            "publish crates",
2088        ],
2089    );
2090
2091    if asks_cleanup {
2092        Some("clean")
2093    } else if asks_package {
2094        Some("package_windows")
2095    } else if asks_release {
2096        Some("release")
2097    } else {
2098        None
2099    }
2100}
2101
2102pub fn mentions_symbol_search(user_input: &str) -> bool {
2103    let lower = user_input.to_lowercase();
2104    contains_any(
2105        &lower,
2106        &[
2107            "find where",
2108            "who calls",
2109            "who uses",
2110            "where is",
2111            "is defined",
2112            "is used",
2113            "find definition",
2114            "find references",
2115            "go to definition",
2116        ],
2117    ) && contains_any(
2118        &lower,
2119        &[
2120            "function", "struct", "variable", "symbol", "method", "type", "trait", "module",
2121        ],
2122    )
2123}
2124
2125pub fn mentions_commit_intent(user_input: &str) -> bool {
2126    let lower = user_input.to_lowercase();
2127    contains_any(
2128        &lower,
2129        &[
2130            "git commit",
2131            "commit my",
2132            "commit the",
2133            "commit changes",
2134            "save my progress to git",
2135        ],
2136    )
2137}
2138
2139pub fn preferred_workspace_workflow(user_input: &str) -> Option<&'static str> {
2140    let lower = user_input.to_ascii_lowercase();
2141    let asks_project_scope = contains_any(
2142        &lower,
2143        &[
2144            "this repo",
2145            "this repository",
2146            "this project",
2147            "current project",
2148            "current repo",
2149            "workspace",
2150            "in this folder",
2151            "here",
2152        ],
2153    );
2154    let asks_build = contains_any(
2155        &lower,
2156        &[
2157            "run the build",
2158            "build this project",
2159            "build this repo",
2160            "run build",
2161            "compile this project",
2162            "cargo build",
2163            "npm run build",
2164            "pnpm run build",
2165            "yarn build",
2166            "go build",
2167            "gradlew build",
2168        ],
2169    );
2170    let asks_test = contains_any(
2171        &lower,
2172        &[
2173            "run the tests",
2174            "run tests",
2175            "test this project",
2176            "test this repo",
2177            "run the test suite",
2178            "cargo test",
2179            "npm test",
2180            "pnpm test",
2181            "yarn test",
2182            "pytest",
2183            "go test",
2184            "gradlew test",
2185        ],
2186    );
2187    let asks_lint = contains_any(
2188        &lower,
2189        &[
2190            "run lint",
2191            "lint this project",
2192            "lint this repo",
2193            "cargo clippy",
2194            "npm run lint",
2195            "pnpm run lint",
2196            "yarn lint",
2197        ],
2198    );
2199    let asks_fix = contains_any(
2200        &lower,
2201        &[
2202            "run fix",
2203            "fix formatting",
2204            "run formatter",
2205            "cargo fmt",
2206            "npm run fix",
2207            "pnpm run fix",
2208            "yarn fix",
2209        ],
2210    );
2211    let asks_script = {
2212        let is_make_file_op = lower.contains("make a folder")
2213            || lower.contains("make a directory")
2214            || lower.contains("make a file")
2215            || lower.contains("make a hello.txt")
2216            || lower.contains("make x");
2217
2218        let has_script_keyword = contains_any(
2219            &lower,
2220            &[
2221                "npm run ",
2222                "pnpm run ",
2223                "yarn ",
2224                "bun run ",
2225                "make ",
2226                "just ",
2227                "task ",
2228                "scripts/",
2229                ".\\scripts\\",
2230                "./scripts/",
2231                ".ps1",
2232                ".sh",
2233                ".py",
2234                ".cmd",
2235                ".bat",
2236            ],
2237        );
2238
2239        has_script_keyword && !is_make_file_op
2240    };
2241
2242    if mentions_symbol_search(user_input) {
2243        Some("lsp_search")
2244    } else if mentions_commit_intent(user_input) {
2245        Some("commit_workflow")
2246    } else if asks_build
2247        && (asks_project_scope
2248            || !contains_any(&lower, &["release.ps1", "package-windows.ps1", "clean.ps1"]))
2249    {
2250        Some("build")
2251    } else if asks_test && asks_project_scope {
2252        Some("test")
2253    } else if asks_lint && asks_project_scope {
2254        Some("lint")
2255    } else if asks_fix && asks_project_scope {
2256        Some("fix")
2257    } else if asks_script && !preferred_maintainer_workflow(user_input).is_some() {
2258        Some("script")
2259    } else if (asks_test || asks_lint || asks_fix)
2260        && !preferred_maintainer_workflow(user_input).is_some()
2261    {
2262        Some(if asks_test {
2263            "test"
2264        } else if asks_lint {
2265            "lint"
2266        } else {
2267            "fix"
2268        })
2269    } else {
2270        None
2271    }
2272}
2273
2274pub(crate) fn looks_like_mutation_request(user_input: &str) -> bool {
2275    let lower = user_input.to_lowercase();
2276    [
2277        "fix ",
2278        "change ",
2279        "edit ",
2280        "modify ",
2281        "update ",
2282        "rename ",
2283        "refactor ",
2284        "patch ",
2285        "rewrite ",
2286        "implement ",
2287        "create a file",
2288        "create file",
2289        "add a file",
2290        "delete ",
2291        "remove ",
2292        "make the change",
2293        "mkdir ",
2294        "touch ",
2295        "create a folder",
2296        "create folder",
2297        "new folder",
2298        "new file",
2299        "write to",
2300        "save this",
2301        "commit ",
2302        "move-item",
2303        "remove-item",
2304        "copy-item",
2305        "rmdir",
2306        "mv ",
2307        "rm ",
2308        "cp ",
2309        "set-content",
2310        "add-content",
2311    ]
2312    .iter()
2313    .any(|needle| lower.contains(needle))
2314}
2315
2316pub(crate) fn is_sovereign_mutation(user_input: &str) -> bool {
2317    let lower = user_input.to_lowercase();
2318    let mentions_location = contains_any(
2319        &lower,
2320        &[
2321            "desktop",
2322            "documents",
2323            "downloads",
2324            "pictures",
2325            "images",
2326            "videos",
2327            "movies",
2328            "music",
2329            "audio",
2330            "temp",
2331            "cache",
2332            "config",
2333            "appdata",
2334        ],
2335    );
2336    let mentions_simple_creation = (lower.contains("make")
2337        || lower.contains("create")
2338        || lower.contains("add")
2339        || lower.contains("new")
2340        || lower.contains("mkdir")
2341        || lower.contains("generate"))
2342        && (lower.contains("folder")
2343            || lower.contains("directory")
2344            || lower.contains("project area")
2345            || lower.contains("file"));
2346
2347    mentions_location && mentions_simple_creation
2348}
2349
2350pub fn classify_query_intent(workflow_mode: WorkflowMode, user_input: &str) -> QueryIntent {
2351    let lower = user_input.to_lowercase();
2352    let trimmed = user_input.trim().to_ascii_lowercase();
2353
2354    let mentions_runtime_trace = contains_any(
2355        &lower,
2356        &[
2357            "trace",
2358            "how does",
2359            "what are the main runtime subsystems",
2360            "how does a user message move",
2361            "separate normal assistant output",
2362            "session reset behavior",
2363            "file references",
2364            "event types",
2365            "channels",
2366        ],
2367    );
2368    let anti_guess = contains_any(&lower, &["do not guess", "if you are unsure"]);
2369    let capability_mode = mentions_capability_question(&lower);
2370    let capability_needs_repo =
2371        capability_mode && capability_question_requires_repo_inspection(&lower);
2372    let host_inspection_mode = preferred_host_inspection_topic(&lower).is_some();
2373    let maintainer_workflow_mode = preferred_maintainer_workflow(&lower).is_some();
2374    let workspace_workflow_mode =
2375        preferred_workspace_workflow(&lower).is_some() && !maintainer_workflow_mode;
2376    let toolchain_mode = contains_any(
2377        &lower,
2378        &[
2379            "tooling discipline",
2380            "best read-only toolchain",
2381            "identify the best tools you actually have",
2382            "concrete read-only investigation plan",
2383            "do not execute the plan",
2384            "available repo-inspection tools",
2385            "tool choice discipline",
2386            "what tools would you choose first",
2387        ],
2388    ) || (lower.contains("which tools") && lower.contains("why"))
2389        || (lower.contains("when would you choose") && lower.contains("tool"));
2390    let architecture_overview_mode = {
2391        let architecture_signals = contains_any(
2392            &lower,
2393            &[
2394                "architecture overview",
2395                "architecture walkthrough",
2396                "full architecture",
2397                "runtime walkthrough",
2398                "control flow",
2399                "tool routing",
2400                "workflow modes",
2401                "repo map behavior",
2402                "mcp policy",
2403                "prompt budgeting",
2404                "compaction",
2405                "file ownership",
2406                "owner file",
2407                "project structure",
2408                "repository structure",
2409            ],
2410        );
2411        let broad = contains_any(
2412            &lower,
2413            &[
2414                "full detailed",
2415                "all in one answer",
2416                "concrete file ownership",
2417                "walk me through",
2418                "major runtime pieces",
2419                "which files own",
2420                "how",
2421                "explain",
2422                "overview",
2423            ],
2424        );
2425        (architecture_signals && broad)
2426            || (lower.contains("runtime")
2427                && lower.contains("workflow")
2428                && (lower.contains("architecture") || lower.contains("tool routing")))
2429            || mentions_broad_system_walkthrough(&lower)
2430    };
2431
2432    let direct_answer = if trimmed == "/about" || mentions_creator_question(&lower) {
2433        Some(DirectAnswerKind::About)
2434    } else if matches!(
2435        trimmed.as_str(),
2436        "who are you" | "who are you?" | "what are you" | "what are you?"
2437    ) || (lower.contains("what is hematite") && !lower.contains("lm studio"))
2438    {
2439        Some(DirectAnswerKind::Identity)
2440    } else if (mentions_stable_product_surface(&lower) || mentions_product_truth_routing(&lower))
2441        && contains_any(
2442            &lower,
2443            &[
2444                "how hematite answers",
2445                "how does hematite answer",
2446                "how hematite handles",
2447                "how does hematite handle",
2448                "how hematite decides",
2449                "how does hematite decide",
2450                "decides whether",
2451                "decide whether",
2452            ],
2453        )
2454    {
2455        Some(DirectAnswerKind::ProductSurface)
2456    } else if mentions_reset_commands(&lower)
2457        && contains_any(
2458            &lower,
2459            &[
2460                "exact difference",
2461                "difference between",
2462                "explain the exact difference",
2463                "what is the difference",
2464            ],
2465        )
2466    {
2467        Some(DirectAnswerKind::SessionResetSemantics)
2468    } else if (lower.contains("reasoning output") || lower.contains("reasoning"))
2469        && contains_any(
2470            &lower,
2471            &["visible chat output", "visible chat", "chat output"],
2472        )
2473    {
2474        Some(DirectAnswerKind::ReasoningSplit)
2475    } else if lower.contains("/ask")
2476        && lower.contains("/code")
2477        && lower.contains("/architect")
2478        && lower.contains("/read-only")
2479        && lower.contains("/auto")
2480        && contains_any(&lower, &["difference", "differences", "what are"])
2481    {
2482        Some(DirectAnswerKind::WorkflowModes)
2483    } else if lower.contains(".hematite/settings.json")
2484        && lower.contains("gemma_native_auto")
2485        && lower.contains("gemma_native_formatting")
2486    {
2487        Some(DirectAnswerKind::GemmaNativeSettings)
2488    } else if contains_any(
2489        &lower,
2490        &[
2491            "skip verification",
2492            "skip build verification",
2493            "commit it immediately",
2494            "commit immediately",
2495        ],
2496    ) && contains_any(
2497        &lower,
2498        &[
2499            "make a code change",
2500            "make the change",
2501            "change the code",
2502            "edit the code",
2503            "edit a file",
2504            "implement",
2505        ],
2506    ) {
2507        Some(DirectAnswerKind::UnsafeWorkflowPressure)
2508    } else if contains_any(&lower, &["/gemma-native", "gemma native"])
2509        && contains_any(&lower, &["what does", "what is", "how does", "what do"])
2510    {
2511        Some(DirectAnswerKind::GemmaNative)
2512    } else if lower.contains("verify_build")
2513        && lower.contains(".hematite/settings.json")
2514        && contains_any(
2515            &lower,
2516            &["build", "test", "lint", "fix", "verification commands"],
2517        )
2518    {
2519        Some(DirectAnswerKind::VerifyProfiles)
2520    } else if (lower.contains("carry forward by default")
2521        || lower.contains("session memory should you carry forward")
2522        || (lower.contains("carry forward")
2523            && contains_any(
2524                &lower,
2525                &[
2526                    "besides the active task",
2527                    "blocker",
2528                    "compacts",
2529                    "recovers from a blocker",
2530                    "session state",
2531                ],
2532            )))
2533        && contains_any(
2534            &lower,
2535            &[
2536                "restarted hematite",
2537                "restarted",
2538                "avoid carrying forward",
2539                "session state",
2540                "active task",
2541                "blocker",
2542                "compacts",
2543                "recovers from a blocker",
2544            ],
2545        )
2546    {
2547        Some(DirectAnswerKind::SessionMemory)
2548    } else if contains_any(
2549        &lower,
2550        &[
2551            "recovery recipe",
2552            "recovery recipes",
2553            "recovery step",
2554            "recovery steps",
2555        ],
2556    ) && contains_any(
2557        &lower,
2558        &[
2559            "blocker",
2560            "runtime failure",
2561            "degrades",
2562            "context window",
2563            "context-window",
2564            "operator",
2565        ],
2566    ) {
2567        Some(DirectAnswerKind::RecoveryRecipes)
2568    } else if !architecture_overview_mode
2569        && contains_any(
2570            &lower,
2571            &[
2572                "mcp server health",
2573                "mcp runtime state",
2574                "mcp lifecycle",
2575                "mcp state",
2576                "mcp healthy",
2577                "mcp degraded",
2578                "mcp failed",
2579            ],
2580        )
2581    {
2582        Some(DirectAnswerKind::McpLifecycle)
2583    } else if contains_any(
2584        &lower,
2585        &[
2586            "allowed, denied, or require approval",
2587            "allowed denied or require approval",
2588            "allow, ask, or deny",
2589            "tool call should be allowed",
2590            "authorization logic",
2591            "workspace trust",
2592            "trust-allowlisted",
2593        ],
2594    ) {
2595        Some(DirectAnswerKind::AuthorizationPolicy)
2596    } else if contains_any(
2597        &lower,
2598        &[
2599            "tool classes",
2600            "tool class",
2601            "flat tool list",
2602            "runtime tool classes",
2603            "different runtime tool classes",
2604        ],
2605    ) || (lower.contains("repo reads")
2606        && lower.contains("repo writes")
2607        && contains_any(
2608            &lower,
2609            &[
2610                "verification tools",
2611                "git tools",
2612                "external mcp tools",
2613                "different runtime",
2614            ],
2615        ))
2616    {
2617        Some(DirectAnswerKind::ToolClasses)
2618    } else if contains_any(
2619        &lower,
2620        &[
2621            "built-in tool catalog",
2622            "builtin tool catalog",
2623            "builtin-tool dispatch",
2624            "built-in tool dispatch",
2625            "tool registry ownership",
2626            "which file now owns",
2627        ],
2628    ) && contains_any(
2629        &lower,
2630        &[
2631            "tool catalog",
2632            "dispatch path",
2633            "dispatch",
2634            "tool registry",
2635            "owns",
2636        ],
2637    ) {
2638        Some(DirectAnswerKind::ToolRegistryOwnership)
2639    } else if (lower.contains("other coding languages")
2640        || lower.contains("what languages")
2641        || lower.contains("know other languages"))
2642        && contains_any(
2643            &lower,
2644            &[
2645                "capable of making projects",
2646                "can you make projects",
2647                "can you build projects",
2648            ],
2649        )
2650    {
2651        Some(DirectAnswerKind::LanguageCapability)
2652    } else if workflow_mode == WorkflowMode::Architect
2653        && (lower.contains("session reset")
2654            || (lower.contains("/clear") && lower.contains("/new") && lower.contains("/forget")))
2655        && contains_any(&lower, &["redesign", "clearer", "easier", "understand"])
2656    {
2657        Some(DirectAnswerKind::ArchitectSessionResetPlan)
2658    } else if toolchain_mode
2659        && lower.contains("read-only")
2660        && contains_any(
2661            &lower,
2662            &[
2663                "tooling discipline",
2664                "investigation plan",
2665                "best read-only toolchain",
2666                "tool choice discipline",
2667                "what tools would you choose first",
2668            ],
2669        )
2670    {
2671        Some(DirectAnswerKind::Toolchain)
2672    } else if host_inspection_mode && mentions_host_inspection_question(&lower) {
2673        Some(DirectAnswerKind::HostInspection)
2674    } else {
2675        None
2676    };
2677
2678    let sovereign_mode = is_sovereign_mutation(user_input);
2679
2680    let primary_class = if direct_answer.is_some()
2681        || mentions_stable_product_surface(&lower)
2682        || mentions_product_truth_routing(&lower)
2683    {
2684        QueryIntentClass::ProductTruth
2685    } else if architecture_overview_mode {
2686        QueryIntentClass::RepoArchitecture
2687    } else if toolchain_mode {
2688        QueryIntentClass::Toolchain
2689    } else if capability_mode {
2690        QueryIntentClass::Capability
2691    } else if mentions_runtime_trace || anti_guess || lower.contains("read-only") {
2692        QueryIntentClass::RuntimeDiagnosis
2693    } else if looks_like_mutation_request(user_input) {
2694        QueryIntentClass::Implementation
2695    } else {
2696        QueryIntentClass::Unknown
2697    };
2698
2699    QueryIntent {
2700        primary_class,
2701        direct_answer,
2702        grounded_trace_mode: mentions_runtime_trace || lower.contains("read-only") || anti_guess,
2703        capability_mode,
2704        capability_needs_repo,
2705        toolchain_mode,
2706        host_inspection_mode,
2707        maintainer_workflow_mode: maintainer_workflow_mode && !sovereign_mode,
2708        workspace_workflow_mode: workspace_workflow_mode && !sovereign_mode,
2709        architecture_overview_mode,
2710        sovereign_mode,
2711        surgical_filesystem_mode: is_simple_surgical_filesystem_request(user_input),
2712    }
2713}
2714
2715fn is_simple_surgical_filesystem_request(user_input: &str) -> bool {
2716    let lower = user_input.to_lowercase();
2717    let mentions_creation = contains_any(
2718        &lower,
2719        &[
2720            "make a folder",
2721            "make a directory",
2722            "make a file",
2723            "create a folder",
2724            "create a directory",
2725            "create a file",
2726            "new folder",
2727            "new directory",
2728        ],
2729    );
2730    let mentions_sovereign = contains_any(
2731        &lower,
2732        &[
2733            "@desktop",
2734            "@documents",
2735            "@downloads",
2736            "@home",
2737            "~/",
2738            "@temp",
2739        ],
2740    );
2741
2742    mentions_creation || mentions_sovereign
2743}
2744
2745pub(crate) fn is_capability_probe_tool(name: &str) -> bool {
2746    matches!(
2747        name,
2748        "read_file"
2749            | "inspect_lines"
2750            | "list_files"
2751            | "grep_files"
2752            | "lsp_definitions"
2753            | "lsp_references"
2754            | "lsp_hover"
2755            | "lsp_search_symbol"
2756            | "lsp_get_diagnostics"
2757            | "trace_runtime_flow"
2758            | "auto_pin_context"
2759            | "list_pinned"
2760    )
2761}
2762
2763/// Returns true when the user's query involves computation that must be exact —
2764/// checksums, financial math, statistics, date arithmetic, algorithmic verification, etc.
2765/// Used by the harness to inject a pre-turn nudge toward run_code instead of model memory.
2766pub fn needs_computation_sandbox(user_input: &str) -> bool {
2767    let lower = user_input.to_lowercase();
2768    let hash_or_checksum = lower.contains("sha")
2769        || lower.contains("md5")
2770        || lower.contains("checksum")
2771        || lower.contains("crc")
2772        || lower.contains("hash")
2773        || lower.contains("fingerprint");
2774    let financial =
2775        (lower.contains("calculat") || lower.contains("compute") || lower.contains("what is"))
2776            && (lower.contains("percent")
2777                || lower.contains("%")
2778                || lower.contains("interest")
2779                || lower.contains("compound")
2780                || lower.contains("roi")
2781                || lower.contains("tax")
2782                || lower.contains("discount")
2783                || lower.contains("profit")
2784                || lower.contains("loss"));
2785    let statistics = lower.contains("standard deviation")
2786        || lower.contains("std dev")
2787        || lower.contains("mean of")
2788        || lower.contains("median of")
2789        || lower.contains("average of")
2790        || lower.contains("variance")
2791        || lower.contains("regression")
2792        || lower.contains("correlation");
2793    let date_math = (lower.contains("how many days")
2794        || lower.contains("days between")
2795        || lower.contains("days until")
2796        || lower.contains("days since")
2797        || lower.contains("unix timestamp")
2798        || lower.contains("epoch")
2799        || lower.contains("time zone")
2800        || lower.contains("timezone"))
2801        && (lower.contains("date")
2802            || lower.contains("day")
2803            || lower.contains("timestamp")
2804            || lower.contains("time"));
2805    let algorithmic = lower.contains("is prime")
2806        || lower.contains("prime number")
2807        || lower.contains("factori")
2808        || lower.contains("fibonacci")
2809        || lower.contains("factorial")
2810        || lower.contains("sort this")
2811        || lower.contains("verify this algorithm")
2812        || lower.contains("run this code")
2813        || lower.contains("execute this");
2814    let unit_conversion = (lower.contains("convert") || lower.contains("how many"))
2815        && (lower.contains(" bytes")
2816            || lower.contains(" kb")
2817            || lower.contains(" mb")
2818            || lower.contains(" gb")
2819            || lower.contains(" tb")
2820            || lower.contains("gigabyte")
2821            || lower.contains("megabyte")
2822            || lower.contains("celsius")
2823            || lower.contains("fahrenheit")
2824            || lower.contains("kelvin")
2825            || lower.contains("kilometers")
2826            || lower.contains("miles")
2827            || lower.contains("pounds")
2828            || lower.contains("kilograms"));
2829    hash_or_checksum || financial || statistics || date_math || algorithmic || unit_conversion
2830}
2831
2832#[cfg(test)]
2833mod tests {
2834    use super::*;
2835
2836    #[test]
2837    fn classify_query_intent_routes_creator_questions_to_about() {
2838        let intent = classify_query_intent(WorkflowMode::Auto, "Who created Hematite?");
2839        assert_eq!(intent.direct_answer, Some(DirectAnswerKind::About));
2840
2841        let intent = classify_query_intent(WorkflowMode::Auto, "/about");
2842        assert_eq!(intent.direct_answer, Some(DirectAnswerKind::About));
2843    }
2844
2845    #[test]
2846    fn classify_query_intent_marks_maintainer_workflow_requests() {
2847        let intent = classify_query_intent(
2848            WorkflowMode::Auto,
2849            "Run my cleanup scripts and prune old artifacts.",
2850        );
2851        assert!(intent.maintainer_workflow_mode);
2852        assert_eq!(
2853            preferred_maintainer_workflow("Rebuild the local portable and update PATH."),
2854            Some("package_windows")
2855        );
2856        assert_eq!(
2857            preferred_maintainer_workflow("Run the release flow and publish crates."),
2858            Some("release")
2859        );
2860    }
2861
2862    #[test]
2863    fn classify_query_intent_marks_workspace_workflow_requests() {
2864        let intent = classify_query_intent(WorkflowMode::Auto, "Run the tests in this project.");
2865        assert!(intent.workspace_workflow_mode);
2866        assert_eq!(
2867            preferred_workspace_workflow("Run the tests in this project."),
2868            Some("test")
2869        );
2870        assert_eq!(
2871            preferred_workspace_workflow("Run npm run dev in this repo."),
2872            Some("script")
2873        );
2874    }
2875
2876    #[test]
2877    fn test_overclocker_routing() {
2878        assert_eq!(
2879            preferred_host_inspection_topic("How's my silicon health looking?"),
2880            Some("overclocker")
2881        );
2882        assert_eq!(
2883            preferred_host_inspection_topic("Show me GPU clocks"),
2884            Some("overclocker")
2885        );
2886        assert_eq!(
2887            preferred_host_inspection_topic("nvidia stats"),
2888            Some("overclocker")
2889        );
2890        assert_eq!(
2891            preferred_host_inspection_topic("Show me GPU voltage telemetry"),
2892            Some("overclocker")
2893        );
2894        assert_eq!(
2895            preferred_host_inspection_topic("What are my CPU and GPU volts right now?"),
2896            Some("overclocker")
2897        );
2898    }
2899
2900    #[test]
2901    fn test_gpu_throttle_routing() {
2902        assert_eq!(
2903            preferred_host_inspection_topic("Is my GPU currently throttled and why?"),
2904            Some("overclocker")
2905        );
2906        assert_eq!(
2907            preferred_host_inspection_topic("Tell me if my GPU is throttled"),
2908            Some("overclocker")
2909        );
2910        assert_eq!(
2911            preferred_host_inspection_topic("Is the GPU overheating?"),
2912            Some("overclocker")
2913        );
2914    }
2915
2916    #[test]
2917    fn test_host_inspection_gateway() {
2918        assert!(mentions_host_inspection_question("is my gpu throttled?"));
2919        assert!(mentions_host_inspection_question(
2920            "check vram and silicon health"
2921        ));
2922        assert!(mentions_host_inspection_question("nvidia stats"));
2923
2924        // Negative tests: General coding/repo questions should NOT trigger the gate
2925        assert!(!mentions_host_inspection_question("What is a Rust macro?"));
2926        assert!(!mentions_host_inspection_question(
2927            "Explain the repository structure."
2928        ));
2929        assert!(!mentions_host_inspection_question("how do I build this?"));
2930        assert!(!mentions_host_inspection_question(
2931            "is this code efficient?"
2932        ));
2933    }
2934}