Skip to main content

hematite/agent/
fix_recipes.rs

1use std::fmt::Write as _;
2
3pub struct Recipe {
4    pub severity: &'static str,
5    pub title: &'static str,
6    pub steps: &'static [&'static str],
7    pub dig_deeper: Option<&'static str>,
8}
9
10struct RecipeAc {
11    ac: aho_corasick::AhoCorasick,
12    recipe_indices: Vec<usize>,
13}
14
15static RECIPE_AC: std::sync::OnceLock<RecipeAc> = std::sync::OnceLock::new();
16
17fn recipe_ac() -> &'static RecipeAc {
18    RECIPE_AC.get_or_init(|| {
19        let total: usize = ALL_RECIPES.iter().map(|e| e.triggers.len()).sum();
20        let mut patterns: Vec<&str> = Vec::with_capacity(total);
21        let mut recipe_indices: Vec<usize> = Vec::with_capacity(total);
22        for (i, entry) in ALL_RECIPES.iter().enumerate() {
23            for &trigger in entry.triggers {
24                patterns.push(trigger);
25                recipe_indices.push(i);
26            }
27        }
28        RecipeAc {
29            ac: aho_corasick::AhoCorasick::new(&patterns).expect("valid patterns"),
30            recipe_indices,
31        }
32    })
33}
34
35pub fn match_recipes(output: &str) -> Vec<&'static Recipe> {
36    let lower = output.to_ascii_lowercase();
37    let state = recipe_ac();
38    let mut seen = std::collections::HashSet::new();
39    let mut matches: Vec<&'static Recipe> = Vec::new();
40    for mat in state.ac.find_iter(&lower) {
41        let idx = state.recipe_indices[mat.pattern().as_usize()];
42        if seen.insert(idx) {
43            matches.push(&ALL_RECIPES[idx].recipe);
44        }
45    }
46    matches
47}
48
49pub fn all_recipes() -> impl Iterator<Item = &'static Recipe> {
50    ALL_RECIPES.iter().map(|e| &e.recipe)
51}
52
53fn collect_unique_recipes(outputs: &[(&str, &str)]) -> Vec<&'static Recipe> {
54    let mut all_recipes: Vec<&'static Recipe> = Vec::new();
55    let mut seen_titles = std::collections::HashSet::new();
56    for (_label, output) in outputs {
57        for recipe in match_recipes(output) {
58            if seen_titles.insert(recipe.title) {
59                all_recipes.push(recipe);
60            }
61        }
62    }
63    all_recipes
64}
65
66struct RecipeEntry {
67    triggers: &'static [&'static str],
68    recipe: Recipe,
69}
70
71static ALL_RECIPES: &[RecipeEntry] = &[
72    // ── Disk / Storage ────────────────────────────────────────────────────────
73    RecipeEntry {
74        triggers: &["very low", "disk:", "free space"],
75        recipe: Recipe {
76            severity: "ACTION",
77            title: "Low disk space",
78            steps: &[
79                "Open Disk Cleanup: press Win+R → type 'cleanmgr' → select C: → check all boxes including 'Windows Update Cleanup'",
80                "Empty the Recycle Bin: right-click desktop icon → Empty Recycle Bin",
81                "Clear Temp folder: press Win+R → type '%temp%' → Ctrl+A → Delete (skip files in use)",
82                "Check largest folders: open PowerShell → Get-ChildItem C:\\ -Recurse -ErrorAction SilentlyContinue | Sort-Object Length -Descending | Select-Object -First 20 FullName, Length",
83                "If space is still tight, run: winget install -e --id Microsoft.PowerToys then use PowerToys Disk Space Analyzer",
84            ],
85            dig_deeper: Some("storage"),
86        },
87    },
88    RecipeEntry {
89        triggers: &["disk_health", "smart", "predictive failure", "wear"],
90        recipe: Recipe {
91            severity: "ACTION",
92            title: "Drive health warning — possible failure",
93            steps: &[
94                "Back up your important files immediately before doing anything else",
95                "Verify the SMART status: open PowerShell (admin) → Get-PhysicalDisk | Select FriendlyName, HealthStatus, OperationalStatus",
96                "If HealthStatus is 'Unhealthy' or 'Warning', replace the drive — do not wait",
97                "For SSDs: check manufacturer's NVMe/SSD tool (Samsung Magician, Crucial Storage Executive, etc.) for wear level",
98            ],
99            dig_deeper: Some("disk_health"),
100        },
101    },
102
103    // ── Reboot ────────────────────────────────────────────────────────────────
104    RecipeEntry {
105        triggers: &["pending reboot", "restart when convenient", "reboot required"],
106        recipe: Recipe {
107            severity: "INVESTIGATE",
108            title: "Restart required",
109            steps: &[
110                "Save your work and restart the computer — pending file operations and updates cannot apply until you do",
111                "After restarting, run this report again to confirm the reboot flag cleared",
112                "If the flag persists after a restart, check Windows Update: Settings → Windows Update → View update history → look for stuck installs",
113            ],
114            dig_deeper: Some("pending_reboot"),
115        },
116    },
117
118    // ── Event log errors ──────────────────────────────────────────────────────
119    RecipeEntry {
120        triggers: &["critical/error event", "error events in windows event log", "critical error"],
121        recipe: Recipe {
122            severity: "INVESTIGATE",
123            title: "Windows event log errors detected",
124            steps: &[
125                "Find the top error sources: PowerShell → Get-WinEvent -FilterHashtable @{LogName='System','Application';Level=1,2} -MaxEvents 100 | Group-Object ProviderName | Sort-Object Count -Descending | Select -First 10",
126                "One crashing service or driver usually causes most of the noise — focus on the source with the highest count",
127                "For 'Service Control Manager' errors: check which service is crashing → Get-WinEvent -FilterHashtable @{LogName='System';ProviderName='Service Control Manager';Level=2} -MaxEvents 10 | Select Message",
128                "For application crashes: check AppEvent for the faulting app name → Get-WinEvent -FilterHashtable @{LogName='Application';Level=2} -MaxEvents 10 | Select TimeCreated,Message",
129            ],
130            dig_deeper: Some("log_check"),
131        },
132    },
133
134    // ── Services ──────────────────────────────────────────────────────────────
135    RecipeEntry {
136        triggers: &["critical service", "not running: windefend", "not running: eventlog", "not running: dnscache"],
137        recipe: Recipe {
138            severity: "ACTION",
139            title: "Critical Windows service not running",
140            steps: &[
141                "Open Services: press Win+R → type 'services.msc' → Enter",
142                "Find the stopped service, right-click → Start",
143                "If it fails to start, right-click → Properties → Recovery tab → set 'First failure' to 'Restart the Service'",
144                "For Windows Defender (WinDefend) stopped: open Windows Security → Virus & threat protection → turn on Real-time protection",
145                "If EventLog is stopped, restart is required — this service cannot be started manually once stopped",
146            ],
147            dig_deeper: Some("services"),
148        },
149    },
150
151    // ── Network ───────────────────────────────────────────────────────────────
152    RecipeEntry {
153        triggers: &["internet connectivity: unreachable", "could not ping 1.1.1.1"],
154        recipe: Recipe {
155            severity: "ACTION",
156            title: "No internet connectivity",
157            steps: &[
158                "Check physical connection: is the Ethernet cable plugged in, or is Wi-Fi connected?",
159                "Test gateway reachability: PowerShell → Test-Connection (Get-NetRoute -DestinationPrefix '0.0.0.0/0').NextHop -Count 1",
160                "Flush DNS cache: PowerShell (admin) → Clear-DnsClientCache",
161                "Reset TCP/IP stack: PowerShell (admin) → netsh int ip reset; netsh winsock reset → then restart",
162                "If on Wi-Fi: forget the network and reconnect, or try 'netsh wlan disconnect' then 'netsh wlan connect name=\"SSID\"'",
163            ],
164            dig_deeper: Some("connectivity"),
165        },
166    },
167    RecipeEntry {
168        triggers: &["high latency", "ms rtt — high latency"],
169        recipe: Recipe {
170            severity: "MONITOR",
171            title: "High network latency detected",
172            steps: &[
173                "Run a traceroute to find where the delay is: PowerShell → tracert 1.1.1.1",
174                "Check for background bandwidth consumers: Task Manager → Performance → Open Resource Monitor → Network tab",
175                "If on Wi-Fi, check signal strength and try moving closer to the router or switching to 5GHz",
176                "Check your ISP's status page for outages in your area",
177            ],
178            dig_deeper: Some("latency"),
179        },
180    },
181
182    // ── RAM ───────────────────────────────────────────────────────────────────
183    RecipeEntry {
184        triggers: &["ram:", "very low", "running a bit low", "free of",
185                    "[warning] memory usage is near capacity", "swap activity may slow"],
186        recipe: Recipe {
187            severity: "MONITOR",
188            title: "High memory usage",
189            steps: &[
190                "Find the top RAM consumers: Task Manager → Memory column (sort descending)",
191                "Close unused browser tabs — each tab can consume 100–500 MB",
192                "Check for memory leaks: if one process is growing over time without release, restart it",
193                "Disable startup programs that aren't needed: Task Manager → Startup tab → disable high-impact items",
194                "If consistently above 85% with normal usage, consider adding RAM",
195            ],
196            dig_deeper: Some("resource_load"),
197        },
198    },
199
200    // ── Thermal ───────────────────────────────────────────────────────────────
201    RecipeEntry {
202        triggers: &["very high", "check cooling", "elevated under load", "°c — very high",
203                    "[warning] cpu load is extremely high", "throttle reason:"],
204        recipe: Recipe {
205            severity: "ACTION",
206            title: "CPU running hot",
207            steps: &[
208                "Shut down and clean dust from fans and heatsink with compressed air — this is the fix 90% of the time",
209                "Check that all fan headers are connected and fans are spinning on boot",
210                "Verify thermal paste on CPU heatsink — if it's more than 4 years old and temperatures are high, repaste",
211                "In BIOS: confirm fan curve is not set to 'Silent' mode — switch to 'Standard' or 'Performance'",
212                "Check for CPU throttling: PowerShell → Get-WmiObject -Class Win32_Processor | Select Name,CurrentClockSpeed,MaxClockSpeed — if Current is much lower than Max under load, it's throttling",
213            ],
214            dig_deeper: Some("thermal"),
215        },
216    },
217
218    // ── Security ──────────────────────────────────────────────────────────────
219    RecipeEntry {
220        triggers: &["real-time protection: disabled", "defender.*disabled", "firewall.*off"],
221        recipe: Recipe {
222            severity: "ACTION",
223            title: "Windows security protection disabled",
224            steps: &[
225                "Re-enable Defender real-time protection: Windows Security → Virus & threat protection → turn on Real-time protection",
226                "If Defender shows as disabled by a third-party antivirus, ensure that AV is up to date and its own real-time protection is on",
227                "Re-enable Windows Firewall: Control Panel → Windows Defender Firewall → Turn Windows Defender Firewall on or off → turn on for all profiles",
228                "Run a quick scan: Windows Security → Virus & threat protection → Quick scan",
229            ],
230            dig_deeper: Some("security"),
231        },
232    },
233    RecipeEntry {
234        triggers: &["threat detected", "quarantine", "malware", "virus found"],
235        recipe: Recipe {
236            severity: "ACTION",
237            title: "Threat detected by Windows Defender",
238            steps: &[
239                "Open Windows Security → Virus & threat protection → Protection history → review detected threats",
240                "If action is 'Quarantined', Defender has contained it — review and remove from quarantine",
241                "Run a full offline scan: Windows Security → Virus & threat protection → Scan options → Microsoft Defender Offline scan",
242                "Change passwords for any accounts accessed on this machine after the infection date",
243                "Check browser extensions for anything you didn't install",
244            ],
245            dig_deeper: Some("defender_quarantine"),
246        },
247    },
248
249    // ── Windows Update ────────────────────────────────────────────────────────
250    RecipeEntry {
251        triggers: &["windows update", "pending update", "update.*required"],
252        recipe: Recipe {
253            severity: "INVESTIGATE",
254            title: "Windows updates pending",
255            steps: &[
256                "Open Settings → Windows Update → Check for updates",
257                "Install all available updates, then restart when prompted",
258                "If updates are stuck: PowerShell (admin) → net stop wuauserv; net stop bits; net start wuauserv; net start bits",
259                "If stuck for more than 24 hours: run the Windows Update Troubleshooter from Settings → System → Troubleshoot → Other troubleshooters",
260            ],
261            dig_deeper: Some("updates"),
262        },
263    },
264
265    // ── Device / driver errors ────────────────────────────────────────────────
266    RecipeEntry {
267        triggers: &["yellow bang", "pnp error", "configmanager error", "error code 43", "error code 10", "error code 28", "device problem", "driver error"],
268        recipe: Recipe {
269            severity: "ACTION",
270            title: "Hardware device error detected",
271            steps: &[
272                "Open Device Manager: press Win+R → type 'devmgmt.msc' → Enter",
273                "Look for yellow exclamation marks (!) — right-click → Properties → note the error code and device name",
274                "Error Code 43 (USB/GPU): unplug and replug the device, or roll back the driver: right-click → Properties → Driver → Roll Back Driver",
275                "Error Code 10 (failed to start): update the driver — right-click → Update driver → Search automatically",
276                "Error Code 28 (no driver): download the driver from the manufacturer's website (look up the device name + Windows version)",
277                "For recurring errors: run SFC scan → PowerShell (admin) → sfc /scannow",
278            ],
279            dig_deeper: Some("device_health"),
280        },
281    },
282
283    // ── No backup configured ──────────────────────────────────────────────────
284    RecipeEntry {
285        triggers: &["file history: disabled", "no backup configured", "no restore points", "last backup: never", "backup: not configured", "file history.*disabled", "no system restore"],
286        recipe: Recipe {
287            severity: "INVESTIGATE",
288            title: "No backup configured",
289            steps: &[
290                "Enable File History: Settings → System → Storage → Advanced storage settings → Backup options → Add a drive",
291                "Enable System Restore: search 'Create a restore point' → select C: → Configure → turn on protection → OK → Create",
292                "For a full image backup: search 'Backup and Restore (Windows 7)' → Create a system image → choose an external drive",
293                "OneDrive Known Folder Backup covers Desktop/Documents/Pictures: Settings → OneDrive → Backup → Manage backup",
294                "Run your first backup immediately — a backup that has never run has zero value",
295            ],
296            dig_deeper: Some("windows_backup"),
297        },
298    },
299
300    // ── SMB1 enabled ─────────────────────────────────────────────────────────
301    RecipeEntry {
302        triggers: &["smb1 is enabled", "smb1: enabled", "smb1 protocol: enabled", "smb version 1", "smbv1 enabled"],
303        recipe: Recipe {
304            severity: "ACTION",
305            title: "SMB1 protocol enabled — security risk",
306            steps: &[
307                "SMB1 is a deprecated protocol exploited by WannaCry and NotPetya ransomware — disable it immediately",
308                "Disable SMB1: PowerShell (admin) → Set-SmbServerConfiguration -EnableSMB1Protocol $false -Force",
309                "Verify it's off: PowerShell → Get-SmbServerConfiguration | Select EnableSMB1Protocol (should show False)",
310                "If a legacy device (old NAS, printer) stops working after disabling, upgrade its firmware or replace it — do not re-enable SMB1",
311                "Restart required to fully remove the SMB1 listener",
312            ],
313            dig_deeper: Some("shares"),
314        },
315    },
316
317    // ── BitLocker not protecting ──────────────────────────────────────────────
318    RecipeEntry {
319        triggers: &["protection state: off", "bitlocker: off", "bitlocker.*not protecting", "encryption status: fully decrypted", "bitlocker.*disabled"],
320        recipe: Recipe {
321            severity: "MONITOR",
322            title: "Drive encryption not enabled",
323            steps: &[
324                "BitLocker encrypts your drive so data is unreadable if the laptop is lost or stolen — strongly recommended on portable machines",
325                "Enable BitLocker: search 'Manage BitLocker' → Turn on BitLocker for C: → follow the wizard",
326                "Save the recovery key to your Microsoft account or print it — you will need it if Windows can't auto-unlock at boot",
327                "Encryption runs in the background and takes 1–3 hours for a typical drive — the PC remains usable during this time",
328                "Requires TPM 1.2+ or USB key; check: PowerShell → Get-Tpm | Select TpmPresent,TpmReady",
329            ],
330            dig_deeper: Some("bitlocker"),
331        },
332    },
333
334    // ── DNS resolution failing ────────────────────────────────────────────────
335    RecipeEntry {
336        triggers: &["dns resolution: failed", "dns: failed", "dns fail", "dns resolution failed", "could not resolve"],
337        recipe: Recipe {
338            severity: "ACTION",
339            title: "DNS resolution failing",
340            steps: &[
341                "Flush DNS cache: PowerShell (admin) → Clear-DnsClientCache",
342                "Test DNS directly: PowerShell → Resolve-DnsName google.com -Server 8.8.8.8 — if this works, your DNS server is the problem",
343                "Switch to a reliable DNS server: PowerShell (admin) → Set-DnsClientServerAddress -InterfaceAlias 'Wi-Fi' -ServerAddresses ('8.8.8.8','1.1.1.1')",
344                "Check if the DNS client service is running: Get-Service Dnscache | Select Status",
345                "If on a corporate network or VPN, contact IT — split DNS may require the VPN to be connected for internal names to resolve",
346            ],
347            dig_deeper: Some("dns_servers"),
348        },
349    },
350
351    // ── Repeated app crashes ──────────────────────────────────────────────────
352    RecipeEntry {
353        triggers: &["faulting application", "crash count", "crash frequency", "application hang", "faulting module"],
354        recipe: Recipe {
355            severity: "INVESTIGATE",
356            title: "Application crashing repeatedly",
357            steps: &[
358                "Note the faulting application name and module from the report — these are the most important clues",
359                "If the faulting module is ntdll.dll or a system DLL: run SFC to repair Windows files → PowerShell (admin) → sfc /scannow",
360                "If the faulting module is a third-party DLL (e.g. a codec or plugin): uninstall the associated program",
361                "Update or reinstall the crashing application — corrupted installs are a common cause",
362                "Check for conflicting software: antivirus, screen recorders, and overlays (Discord, GeForce Experience) frequently inject into other processes",
363                "If it is a Microsoft Office app: run the Office repair → Control Panel → Programs → right-click Office → Change → Quick Repair",
364            ],
365            dig_deeper: Some("app_crashes"),
366        },
367    },
368
369    // ── Visual C++ / runtime missing ─────────────────────────────────────────
370    RecipeEntry {
371        triggers: &["vcruntime", "msvcr", "0xc000007b", "side-by-side configuration", "missing runtime", "vc++ redistributable"],
372        recipe: Recipe {
373            severity: "ACTION",
374            title: "Visual C++ runtime missing or corrupt",
375            steps: &[
376                "Download and install the latest Visual C++ Redistributable packages (both x64 and x86) from Microsoft: search 'Visual C++ Redistributable downloads'",
377                "Install all available years: 2015–2022 package covers most apps; older apps may need 2013, 2012, or 2010 separately",
378                "If a specific app shows error 0xc000007b: right-click the app → Properties → Compatibility → Run as administrator",
379                "Repair existing runtimes: Control Panel → Programs → find 'Microsoft Visual C++ 20XX' → Repair",
380                "After installing, restart before testing the application again — runtimes must be registered at boot",
381            ],
382            dig_deeper: Some("installed_software"),
383        },
384    },
385
386    // ── Certificate expiring ──────────────────────────────────────────────────
387    RecipeEntry {
388        triggers: &["expiring within 30 days", "expires in", "certificate expir", "cert.*expir"],
389        recipe: Recipe {
390            severity: "INVESTIGATE",
391            title: "Certificate expiring soon",
392            steps: &[
393                "Open Certificate Manager: press Win+R → type 'certmgr.msc' → check Personal → Certificates for the expiring cert",
394                "Note the certificate subject and issuer — determines who you need to contact for renewal",
395                "For personal/S-MIME certificates: renew through your CA or email provider portal",
396                "For web/TLS certificates on a server: generate a new CSR and submit to your CA before expiry",
397                "For code-signing certificates: do not let these lapse — signed binaries will show 'unknown publisher' warnings after expiry",
398            ],
399            dig_deeper: Some("certificates"),
400        },
401    },
402
403    // ── Wi-Fi weak signal ─────────────────────────────────────────────────────
404    RecipeEntry {
405        triggers: &["signal: poor", "weak signal", "rssi: -8", "rssi: -9", "signal strength: poor", "quality: poor", "poor signal"],
406        recipe: Recipe {
407            severity: "MONITOR",
408            title: "Wi-Fi signal weak",
409            steps: &[
410                "Move closer to the router or access point — Wi-Fi degrades quickly through walls and floors",
411                "Switch to 5 GHz band if available — faster and less congested in most home environments (but shorter range than 2.4 GHz)",
412                "Check for interference: microwave ovens, baby monitors, and neighboring networks on the same channel all degrade signal",
413                "Change the router's Wi-Fi channel: log into router admin → Wireless settings → try channels 1, 6, or 11 (2.4 GHz) or auto (5 GHz)",
414                "Update the Wi-Fi adapter driver: Device Manager → Network Adapters → right-click adapter → Update driver",
415                "If signal is consistently poor from a fixed desk, consider a powerline adapter or mesh Wi-Fi node nearby",
416            ],
417            dig_deeper: Some("wifi"),
418        },
419    },
420
421    // ── NTP / time sync failure ───────────────────────────────────────────────
422    RecipeEntry {
423        triggers: &["time sync failed", "sync failed", "clock drift", "ntp.*error", "w32tm.*fail", "ntp source.*unreachable", "time.*not synchronized"],
424        recipe: Recipe {
425            severity: "INVESTIGATE",
426            title: "System clock not synchronizing",
427            steps: &[
428                "Force a sync now: PowerShell (admin) → w32tm /resync /force",
429                "Check the current NTP source: PowerShell → w32tm /query /source",
430                "If source shows 'Local CMOS Clock' or 'Free-running', the time service has lost its server",
431                "Reset to Microsoft's NTP server: PowerShell (admin) → w32tm /config /manualpeerlist:time.windows.com /syncfromflags:manual /reliable:YES /update",
432                "Restart the time service: PowerShell (admin) → Restart-Service w32tm",
433                "If clock drift is large (>5 minutes), some authentication systems (Kerberos, MFA) will fail until synced",
434            ],
435            dig_deeper: Some("ntp"),
436        },
437    },
438
439    // ── Page file missing ─────────────────────────────────────────────────────
440    RecipeEntry {
441        triggers: &["no page file", "pagefile: none", "page file: none", "virtual memory: none", "pagefile not configured", "no pagefile"],
442        recipe: Recipe {
443            severity: "INVESTIGATE",
444            title: "Page file not configured",
445            steps: &[
446                "Windows needs a page file even with plenty of RAM — some apps and crash dumps require it",
447                "Re-enable automatic page file management: search 'Adjust the appearance and performance of Windows' → Advanced → Virtual memory → Change → check 'Automatically manage'",
448                "If manually set: assign at least 1.5× your RAM as maximum size on the system drive",
449                "After changing page file settings, restart is required — changes do not take effect until reboot",
450                "Note: if this machine intentionally has no page file (e.g. a RAM disk setup), verify that was deliberate before changing it",
451            ],
452            dig_deeper: Some("pagefile"),
453        },
454    },
455
456    // ── System file corruption ────────────────────────────────────────────────
457    RecipeEntry {
458        triggers: &["corrupt files found", "autorepairrequired: true", "integrity.*failed", "component store corruption", "sfc.*corrupt", "windows resource protection found corrupt"],
459        recipe: Recipe {
460            severity: "ACTION",
461            title: "Windows system file corruption detected",
462            steps: &[
463                "Run SFC to repair corrupt files: PowerShell (admin) → sfc /scannow (takes 5–15 minutes)",
464                "If SFC reports 'Windows Resource Protection found corrupt files but was unable to fix some of them', run DISM next:",
465                "DISM repair: PowerShell (admin) → DISM /Online /Cleanup-Image /RestoreHealth (requires internet access, 10–30 minutes)",
466                "Run SFC again after DISM completes — DISM provides the source files SFC needs",
467                "Restart after both complete, then check Event Viewer for CBS log: Applications and Services Logs → Microsoft → Windows → Servicing",
468                "If corruption persists after both tools: in-place upgrade repair (Windows Setup without wiping data) is the next step",
469            ],
470            dig_deeper: Some("integrity"),
471        },
472    },
473
474    // ── Service start failure ─────────────────────────────────────────────────
475    RecipeEntry {
476        triggers: &["stopped unexpectedly", "failed to start", "error 1067", "error 1053", "service terminated", "exited with code", "failed to respond"],
477        recipe: Recipe {
478            severity: "INVESTIGATE",
479            title: "Service failed to start or stopped unexpectedly",
480            steps: &[
481                "Find the failing service name in the report, then check its status: PowerShell → Get-Service <ServiceName>",
482                "Read the specific error from the Application/System event log: Event Viewer → Windows Logs → System → filter for Service Control Manager (Event ID 7034 or 7031)",
483                "Try to start it manually: PowerShell (admin) → Start-Service <ServiceName> — note any error message",
484                "Check if the service account has the right permissions: Services console (services.msc) → right-click → Properties → Log On tab",
485                "Look for a dependent service that failed first — a service won't start if something it requires is stopped",
486                "If the service EXE is missing or corrupt, reinstall the application that owns it",
487            ],
488            dig_deeper: Some("services"),
489        },
490    },
491
492    // ── RDP unreachable ───────────────────────────────────────────────────────
493    RecipeEntry {
494        triggers: &["fdenytsconnections: 1", "no enabled rdp firewall", "rdp status: disabled"],
495        recipe: Recipe {
496            severity: "ACTION",
497            title: "Remote Desktop (RDP) is disabled or blocked",
498            steps: &[
499                "Enable RDP: Settings → System → Remote Desktop → Enable Remote Desktop (or PowerShell admin: Set-ItemProperty 'HKLM:\\System\\CurrentControlSet\\Control\\Terminal Server' fDenyTSConnections 0)",
500                "Ensure the RDP firewall rule is enabled: PowerShell (admin) → Enable-NetFirewallRule -DisplayGroup 'Remote Desktop'",
501                "Verify port 3389 is listening after enabling: PowerShell → netstat -an | findstr 3389",
502                "If NLA is required, make sure the connecting user account has the right to log in remotely (must be in Remote Desktop Users group or Administrators)",
503                "Check that Windows Firewall is not blocking the connection — on the host, temporarily allow pings to confirm network path is open",
504                "For cloud VMs: check the security group / NSG allows inbound TCP 3389 from your IP",
505            ],
506            dig_deeper: Some("rdp"),
507        },
508    },
509
510    // ── Windows Update service broken ─────────────────────────────────────────
511    RecipeEntry {
512        triggers: &["wuauserv: stopped", "wuauserv stopped", "windows update: stopped", "update service stopped", "bits: stopped", "bits stopped"],
513        recipe: Recipe {
514            severity: "ACTION",
515            title: "Windows Update service is stopped or broken",
516            steps: &[
517                "Run the Windows Update Troubleshooter: Settings → Update & Security → Troubleshoot → Additional troubleshooters → Windows Update",
518                "Manually restart the update services: PowerShell (admin) → Stop-Service wuauserv, bits, cryptsvc, msiserver → Start-Service wuauserv, bits, cryptsvc",
519                "Clear the update cache if stuck: PowerShell (admin) → Stop-Service wuauserv → Remove-Item C:\\Windows\\SoftwareDistribution\\* -Recurse -Force → Start-Service wuauserv",
520                "Check for conflicting 3rd-party update tools (WSUS, SCCM, Intune policies) that may be disabling updates",
521                "Run the System Update Readiness Tool: DISM /Online /Cleanup-Image /RestoreHealth",
522                "If the service keeps stopping, check Event Viewer → Windows Logs → System for Windows Update Agent errors around the same time",
523            ],
524            dig_deeper: Some("updates"),
525        },
526    },
527
528    // ── Teams cache ───────────────────────────────────────────────────────────
529    RecipeEntry {
530        triggers: &["classic teams cache:", "new teams cache:", "msteams cache:", "teams cache size", "classicteamscache |", "newteamscache |"],
531        recipe: Recipe {
532            severity: "INVESTIGATE",
533            title: "Teams cache — clear to resolve most Teams issues",
534            steps: &[
535                "Quit Teams completely: right-click the Teams icon in the system tray → Quit",
536                "Clear Classic Teams cache: open Run (Win+R) → type %AppData%\\Microsoft\\Teams → delete the contents of: Cache, blob_storage, databases, GPUCache, IndexedDB, Local Storage, tmp",
537                "Clear New Teams (MSTeams) cache: open Run → %LocalAppData%\\Packages\\MSTeams_8wekyb3d8bbwe\\LocalCache\\ → delete all contents",
538                "Restart Teams and sign in — cache rebuilds from the server automatically",
539                "If Teams still fails to sign in after clearing cache, also clear credentials: Credential Manager (Win+R → credmgr.msc) → Windows Credentials → remove all MicrosoftOffice16_Data:SSPI:* entries",
540            ],
541            dig_deeper: Some("teams"),
542        },
543    },
544
545    // ── M365 token broker not running ─────────────────────────────────────────
546    RecipeEntry {
547        triggers: &["token broker: not running", "tokenbroker | status: stopped", "aad broker plugin: not found", "web account manager: not running", "wam: not running", "aad broker: not found"],
548        recipe: Recipe {
549            severity: "ACTION",
550            title: "Microsoft 365 authentication broker not running",
551            steps: &[
552                "The Windows Account Manager (WAM) and AAD Broker are required for M365 sign-in — if they're not running, Teams, Outlook, and OneDrive will loop on sign-in",
553                "Re-register the token broker: PowerShell (admin) → sfc /scannow — this repairs the system files WAM depends on",
554                "Restart the TokenBroker service: PowerShell (admin) → Restart-Service TokenBroker -ErrorAction SilentlyContinue",
555                "If re-registering doesn't help, sign out of all work accounts: Settings → Accounts → Access work or school → disconnect and reconnect your org account",
556                "On Intune/AAD-joined machines: run 'dsregcmd /leave' then 'dsregcmd /join' (admin) to re-register the device — requires network connectivity to Azure AD",
557                "Check for conflicting credential entries: Credential Manager → Windows Credentials → remove stale MicrosoftOffice16_Data:SSPI:* and MicrosoftOffice15_Data:* entries",
558            ],
559            dig_deeper: Some("identity_auth"),
560        },
561    },
562
563    // ── WMI repository corrupt ────────────────────────────────────────────────
564    RecipeEntry {
565        triggers: &["wmi repository is inconsistent", "repository is inconsistent", "wmi: inconsistent", "verifyrepository: inconsistent", "wmi corruption"],
566        recipe: Recipe {
567            severity: "ACTION",
568            title: "WMI repository corrupt — cascading tool failures",
569            steps: &[
570                "WMI corruption breaks PowerShell Get-WmiObject, Defender, Windows Update, and many admin tools — fix it first before investigating other issues",
571                "Stop WMI: PowerShell (admin) → net stop winmgmt /y",
572                "Rebuild the repository: PowerShell (admin) → winmgmt /resetrepository",
573                "Start WMI: PowerShell (admin) → net start winmgmt",
574                "Verify the fix: PowerShell → winmgmt /verifyrepository — should say 'WMI repository is consistent'",
575                "If resetrepository fails, try salvage mode: winmgmt /salvagerepository — this preserves customizations",
576                "Restart the machine after repair — WMI caches are session-scoped and some tools won't see the fix until reboot",
577            ],
578            dig_deeper: Some("wmi_health"),
579        },
580    },
581
582    // ── Windows not activated ─────────────────────────────────────────────────
583    RecipeEntry {
584        triggers: &["license status: unlicensed", "license status: notification", "activation: not activated", "not genuine", "windows is not activated"],
585        recipe: Recipe {
586            severity: "INVESTIGATE",
587            title: "Windows not activated",
588            steps: &[
589                "Check activation status: Settings → System → Activation — note the exact status message",
590                "If you have a product key: Settings → System → Activation → Change product key → enter the 25-character key",
591                "If the key was tied to a Microsoft account: sign in with that Microsoft account and activation should happen automatically over the internet",
592                "Force activation attempt: PowerShell (admin) → slmgr /ato",
593                "If you get error 0xC004F074 (Key Management Service unreachable): you're on a domain with KMS — contact your IT department, the KMS server may be offline",
594                "If you recently changed hardware (motherboard): activation may need to be relinked — use the Activation Troubleshooter in Settings",
595            ],
596            dig_deeper: Some("activation"),
597        },
598    },
599
600    // ── Windows Search not indexing ───────────────────────────────────────────
601    RecipeEntry {
602        triggers: &["wsearch: stopped", "search service: stopped", "wsearch service: stopped", "indexer: stopped", "windows search: stopped"],
603        recipe: Recipe {
604            severity: "INVESTIGATE",
605            title: "Windows Search not running — search won't find files",
606            steps: &[
607                "Start the Windows Search service: PowerShell (admin) → Start-Service WSearch",
608                "Set it to start automatically: PowerShell (admin) → Set-Service WSearch -StartupType Automatic",
609                "If the service won't start: check Event Viewer → Windows Logs → Application → filter for 'Search' for the specific error",
610                "Rebuild the search index: Settings → Privacy & Security → Windows Search → Advanced indexing options → Advanced → Rebuild — takes 15–60 minutes",
611                "If rebuilding doesn't help, reset the index database: Stop-Service WSearch → delete C:\\ProgramData\\Microsoft\\Search\\Data\\Applications\\Windows\\Windows.edb → Start-Service WSearch",
612                "Restart File Explorer after: PowerShell → Stop-Process -Name explorer → Start-Process explorer",
613            ],
614            dig_deeper: Some("search_index"),
615        },
616    },
617
618    // ── OneDrive sync error ───────────────────────────────────────────────────
619    RecipeEntry {
620        triggers: &["sync status: error", "onedrive: not running", "sync errors detected", "onedrive sync error", "known folder backup: not configured"],
621        recipe: Recipe {
622            severity: "INVESTIGATE",
623            title: "OneDrive not syncing",
624            steps: &[
625                "Check the sync status icon in the system tray — hover over it for the specific error message",
626                "Common fix: right-click the OneDrive tray icon → Pause syncing → Resume syncing — resets stuck sync state",
627                "If that doesn't work: right-click the OneDrive tray icon → Settings → Account → Unlink this PC → relink with the same account",
628                "Check for conflicting files: File Explorer → OneDrive folder → look for files with a red X — rename or delete the local copy and let it sync from the cloud",
629                "If the issue is 'Not enough space in OneDrive': manage storage at onedrive.live.com/manage",
630                "Reset OneDrive if all else fails: Win+R → %localappdata%\\Microsoft\\OneDrive\\onedrive.exe /reset — wait 2 minutes, then reopen OneDrive from Start",
631            ],
632            dig_deeper: Some("onedrive"),
633        },
634    },
635
636    // ── Printer offline or stuck queue ────────────────────────────────────────
637    RecipeEntry {
638        triggers: &["status: offline", "pending jobs:", "print spooler: stopped", "spooler: stopped"],
639        recipe: Recipe {
640            severity: "INVESTIGATE",
641            title: "Printer offline or stuck print queue",
642            steps: &[
643                "Check the printer is powered on and connected (USB cable or same Wi-Fi network as the PC)",
644                "Clear the stuck print queue: PowerShell (admin) → Stop-Service Spooler → Remove-Item C:\\Windows\\System32\\spool\\PRINTERS\\* -Force → Start-Service Spooler",
645                "If printer shows Offline: right-click the printer in Settings → Printers & scanners → See what's printing → Printer menu → uncheck 'Use Printer Offline'",
646                "For network printers: verify the printer's IP hasn't changed — print a configuration page from the printer itself to check its current IP",
647                "Re-add the printer if the IP changed: Settings → Bluetooth & devices → Printers & scanners → Add device → Add manually → enter the new IP",
648                "If the Print Spooler service is stopped: PowerShell (admin) → Start-Service Spooler → Set-Service Spooler -StartupType Automatic",
649            ],
650            dig_deeper: Some("printers"),
651        },
652    },
653
654    // ── No Outlook mail profile ───────────────────────────────────────────────
655    RecipeEntry {
656        triggers: &["profile count: 0", "no mail profiles", "mail profile: none", "no profiles configured", "outlook profiles: 0"],
657        recipe: Recipe {
658            severity: "ACTION",
659            title: "No Outlook mail profile — Outlook will not open",
660            steps: &[
661                "Outlook requires at least one mail profile to start — create one from the Mail control panel applet, not from within Outlook",
662                "Open Mail applet: Win+R → type 'control mlcfg32.cpl' (or search 'Mail' in Control Panel) → Show Profiles → Add",
663                "Enter a profile name (e.g. 'Outlook') → Add Account → enter your email address and follow the auto-configuration wizard",
664                "For Exchange/Microsoft 365: the wizard needs network access to find the Autodiscover DNS record — ensure VPN is connected if this is a corporate account",
665                "For manual setup: choose 'Manual setup' → Microsoft Exchange or compatible service → enter server and username from your IT department",
666                "After creating the profile: launch Outlook, sign in if prompted — first launch will take 2–10 minutes to download the mailbox",
667            ],
668            dig_deeper: Some("outlook"),
669        },
670    },
671
672    // ── PrintNightmare not mitigated ──────────────────────────────────────────
673    RecipeEntry {
674        triggers: &["rpcauthnlevelprivacyenabled: 0", "printnightmare rpc mitigation not applied", "point and print allows silent", "finding: printnightmare"],
675        recipe: Recipe {
676            severity: "INVESTIGATE",
677            title: "PrintNightmare (CVE-2021-34527) mitigation not applied",
678            steps: &[
679                "Apply the RPC authentication hardening fix: PowerShell (admin) → Set-ItemProperty 'HKLM:\\SYSTEM\\CurrentControlSet\\Control\\Print' -Name RpcAuthnLevelPrivacyEnabled -Value 1 -Type DWord",
680                "Restrict Point and Print driver installs to administrators: PowerShell (admin) → Set-ItemProperty 'HKLM:\\SOFTWARE\\Policies\\Microsoft\\Windows NT\\Printers\\PointAndPrint' -Name RestrictDriverInstallationToAdministrators -Value 1 -Type DWord",
681                "If the Print Spooler is not needed (e.g. server, workstation that never prints remotely): PowerShell (admin) → Stop-Service Spooler → Set-Service Spooler -StartupType Disabled",
682                "Verify patch KB5004945 or later is installed: check Windows Update history for July 2021 security updates",
683                "Restart the Spooler service after registry changes: PowerShell (admin) → Restart-Service Spooler",
684            ],
685            dig_deeper: Some("print_spooler"),
686        },
687    },
688
689    // ── TCP/IP stack corruption ───────────────────────────────────────────────
690    RecipeEntry {
691        triggers: &["winsock catalog corrupted", "winsock reset", "tcp/ip stack", "netsh int ip reset", "no internet after update", "network stack corrupt"],
692        recipe: Recipe {
693            severity: "ACTION",
694            title: "TCP/IP or Winsock stack needs reset",
695            steps: &[
696                "Open PowerShell as administrator",
697                "Reset the TCP/IP stack: netsh int ip reset",
698                "Reset the Winsock catalog: netsh winsock reset",
699                "Restart the computer — these changes require a reboot to take effect",
700                "After restart, verify internet is restored: ping 1.1.1.1",
701                "If still broken, run: ipconfig /release then ipconfig /renew to get a fresh DHCP lease",
702            ],
703            dig_deeper: Some("connectivity"),
704        },
705    },
706
707    // ── WLAN AutoConfig service stopped ──────────────────────────────────────
708    RecipeEntry {
709        triggers: &["wlansvc: stopped", "wlan autoconfig: stopped", "wlan autoconfig service: stopped", "wireless autoconfig: stopped"],
710        recipe: Recipe {
711            severity: "ACTION",
712            title: "WLAN AutoConfig service stopped — Wi-Fi unavailable",
713            steps: &[
714                "Open PowerShell as administrator",
715                "Start the WLAN AutoConfig service: Start-Service Wlansvc",
716                "Set it to auto-start: Set-Service Wlansvc -StartupType Automatic",
717                "Verify Wi-Fi adapter is visible: Get-NetAdapter | Where-Object {$_.MediaType -eq '802.11'}",
718                "If no Wi-Fi adapter appears after starting the service, check Device Manager for a disabled wireless adapter",
719                "If the service fails to start, update the Wi-Fi adapter driver via Device Manager → right-click adapter → Update driver",
720            ],
721            dig_deeper: Some("wifi"),
722        },
723    },
724
725    // ── BSOD / unexpected shutdown ────────────────────────────────────────────
726    RecipeEntry {
727        triggers: &[
728            "system crashes / unexpected shutdowns:",
729            "bsod (bugcheck)",
730            "unexpected shutdown",
731            "blue screen",
732            "stop code",
733            "critical_process_died",
734            "memory_management",
735            "kernel_security_check_failure",
736            "page_fault_in_nonpaged_area",
737            "irql_not_less_or_equal",
738            "system_thread_exception",
739            "kmode_exception_not_handled",
740            "ntfs_file_system",
741            "bsod after",
742            "keeps crashing",
743            "random restart",
744            "random reboot",
745        ],
746        recipe: Recipe {
747            severity: "ACTION",
748            title: "Blue screen (BSOD) or unexpected shutdown",
749            steps: &[
750                "Find the stop code: Settings → System → About → Blue screen of death stop code, or check Event Viewer (Win+X → Event Viewer → Windows Logs → System → filter for Event ID 41 and 1001)",
751                "Run memory diagnostics: Win+R → type 'mdsched' → restart and test now — leave it running overnight if possible; replace RAM if errors found",
752                "Check for corrupted system files: PowerShell (admin) → sfc /scannow, then DISM /Online /Cleanup-Image /RestoreHealth (takes 15–30 min, requires internet)",
753                "Roll back recently installed drivers: Device Manager → look for recently updated drivers (sort by date) → right-click → Properties → Driver → Roll Back Driver",
754                "Check for Windows Update issues: some BSODs after updates are fixed by the next cumulative update — check Windows Update for pending updates",
755                "For memory-related stop codes (MEMORY_MANAGEMENT, PAGE_FAULT_IN_NONPAGED_AREA): reseat RAM sticks (power off, remove and firmly reinsert each stick)",
756                "Read the minidump for the exact faulting module: PowerShell (admin) → Get-ChildItem C:\\Windows\\Minidump | Sort-Object LastWriteTime -Descending | Select-Object -First 3 FullName — open with WinDbg or upload to https://www.osronline.com/page.cfm?name=analyze",
757                "If BSODs persist: run hematite --inspect disk_health,thermal to rule out failing storage or overheating as the root cause",
758            ],
759            dig_deeper: Some("recent_crashes"),
760        },
761    },
762
763    // ── Webcam / camera not working ───────────────────────────────────────────
764    RecipeEntry {
765        triggers: &[
766            "global: deny",
767            "camera access is globally denied",
768            "no camera devices found via pnp",
769            "camera not working",
770            "webcam not working",
771            "camera not detected",
772            "camera blocked",
773            "camera privacy",
774            "no cameras found",
775        ],
776        recipe: Recipe {
777            severity: "ACTION",
778            title: "Camera / webcam not working or blocked",
779            steps: &[
780                "Check Windows camera privacy first: Settings → Privacy & security → Camera → toggle 'Let apps access your camera' ON",
781                "If the global toggle is already on, check the specific app (Teams, Zoom, etc.) — scroll down on the same page and enable it for the app individually",
782                "Test the camera works at all: Win+R → type 'camera' → open the Camera app — if it works there, the issue is app-specific permissions, not the device",
783                "If Camera app also fails: open Device Manager (Win+X) → Cameras — look for a yellow bang (error) on the device, right-click → Update driver → Search automatically",
784                "If no camera appears in Device Manager at all: check if the camera is physically disabled via a keyboard shortcut (often Fn+F key with a camera icon), or disabled in BIOS/UEFI",
785                "Reinstall the camera driver: Device Manager → Cameras → right-click → Uninstall device (check 'Delete driver' box) → restart PC; Windows re-installs the driver on boot",
786                "For external USB cameras: try a different USB port or USB cable; test on another machine to confirm the hardware is not faulty",
787                "In Teams or Zoom: check in-app settings → Video/Camera device selector — make sure the correct camera is selected (especially if multiple cameras exist)",
788            ],
789            dig_deeper: Some("camera"),
790        },
791    },
792
793    // ── Windows Firewall service stopped ─────────────────────────────────────
794    RecipeEntry {
795        triggers: &["firewall service: stopped", "mpssvc: stopped", "windows firewall service: stopped", "firewall: stopped"],
796        recipe: Recipe {
797            severity: "ACTION",
798            title: "Windows Firewall service stopped — security risk",
799            steps: &[
800                "Open PowerShell as administrator",
801                "Start the Windows Firewall service: Start-Service MpsSvc",
802                "Set it to auto-start: Set-Service MpsSvc -StartupType Automatic",
803                "Verify all profiles are active: Get-NetFirewallProfile | Select Name, Enabled",
804                "If MpsSvc fails to start, check for third-party firewall software that may have disabled it",
805                "In an enterprise environment, the firewall may be managed by Group Policy — run gpresult /r to check",
806            ],
807            dig_deeper: Some("security"),
808        },
809    },
810
811    // ── No audio / sound not working ─────────────────────────────────────────
812    RecipeEntry {
813        triggers: &["core audio services are not running", "no audio endpoints", "audio service not running", "audiosrv: not running", "no playback devices", "no sound device"],
814        recipe: Recipe {
815            severity: "ACTION",
816            title: "No audio — Windows Audio service or device issue",
817            steps: &[
818                "Restart the Windows Audio service: PowerShell (admin) → Restart-Service Audiosrv -Force",
819                "If the service restarts but no sound, check Device Manager for audio device errors: Win+X → Device Manager → Sound, video and game controllers",
820                "Right-click any device with a yellow bang → Update driver → Search automatically",
821                "Check the audio device is not muted or disabled: right-click volume icon in taskbar → Open Sound settings → check Output device",
822                "For Bluetooth headsets: run hematite --inspect bluetooth to check the AVCTP service and audio crossover state",
823                "If using a USB audio device: unplug and replug the device, or try a different USB port",
824                "Last resort — reinstall the audio driver: Device Manager → Sound → right-click device → Uninstall device → Restart PC (Windows re-installs the driver)",
825            ],
826            dig_deeper: Some("audio"),
827        },
828    },
829
830    // ── Bluetooth pairing/connection issues ──────────────────────────────────
831    RecipeEntry {
832        triggers: &["bluetooth-related services are not fully running", "no bluetooth radio", "bluetooth device issues", "bluetooth adapter not detected", "bthserv: stopped", "bluetooth service not running"],
833        recipe: Recipe {
834            severity: "ACTION",
835            title: "Bluetooth not working — service or hardware issue",
836            steps: &[
837                "Restart the Bluetooth services: PowerShell (admin) → Restart-Service bthserv -Force",
838                "If the service starts but devices won't pair: toggle Bluetooth off then on in Settings → Bluetooth & devices",
839                "Remove the device and re-pair it: Settings → Bluetooth & devices → find the device → Remove → Add device",
840                "If no Bluetooth adapter is detected: check Device Manager → Bluetooth — look for missing or disabled adapters",
841                "For a disabled adapter: right-click → Enable device; for a missing adapter, check if Bluetooth is disabled in BIOS/UEFI or via a physical switch/key combination",
842                "Update the Bluetooth driver: Device Manager → Bluetooth → right-click adapter → Update driver",
843                "For Bluetooth audio: verify the device is set as the default audio output in Settings → System → Sound",
844            ],
845            dig_deeper: Some("bluetooth"),
846        },
847    },
848
849    // ── Windows Installer / app installation failing ──────────────────────────
850    RecipeEntry {
851        triggers: &["msiserver) is disabled", "msiexec.exe is missing", "installer transaction already in progress", "recent installer failures were recorded", "microsoft desktop app installer is missing", "msiserver: disabled"],
852        recipe: Recipe {
853            severity: "ACTION",
854            title: "App installation failing — Windows Installer issue",
855            steps: &[
856                "Re-enable and start the Windows Installer service: PowerShell (admin) → Set-Service msiserver -StartupType Manual; Start-Service msiserver",
857                "If an installer transaction is stuck, clear it: PowerShell (admin) → msiexec /unreg; msiexec /regserver",
858                "If a pending reboot is blocking installs: save your work and restart the computer first",
859                "For winget install failures: ensure Microsoft Desktop App Installer is current — open Microsoft Store → search 'App Installer' → Update",
860                "Clear the Windows Installer temp files: delete C:\\Windows\\Installer\\*.tmp",
861                "If MSI installs still fail after the above, run: sfc /scannow in an admin PowerShell to repair system files that MSI depends on",
862            ],
863            dig_deeper: Some("installer_health"),
864        },
865    },
866
867    // ── VPN not connecting ────────────────────────────────────────────────────
868    RecipeEntry {
869        triggers: &[
870            "vpn adapter detected",
871            "no vpn adapters found",
872            "vpn client service",
873            "vpn tunnel",
874            "vpn not connecting",
875            "vpn disconnecting",
876            "split tunnel",
877            "ras/vpn",
878            "rasman",
879        ],
880        recipe: Recipe {
881            severity: "INVESTIGATE",
882            title: "VPN not connecting or disconnecting",
883            steps: &[
884                "Restart the Remote Access Connection Manager service: PowerShell (admin) → Restart-Service RasMan -Force",
885                "Check for a proxy conflict: Settings → Network & Internet → Proxy — disable 'Automatically detect settings' temporarily and test again",
886                "Flush DNS and reset Winsock (VPN can leave stale routes): PowerShell (admin) → ipconfig /flushdns; netsh winsock reset",
887                "If using a corporate VPN client (Cisco AnyConnect, GlobalProtect, Pulse): reinstall the client or run the client's repair option from Add/Remove Programs",
888                "Check Windows Firewall isn't blocking the VPN ports: run hematite --inspect firewall_rules and look for rules blocking UDP 500, UDP 4500, or TCP 1723",
889                "If the VPN adapter shows but won't authenticate: run hematite --inspect identity_auth to check if the AAD/WAM token broker is healthy",
890                "For WireGuard or OpenVPN: verify the tunnel config file is intact and the remote server is reachable: ping <vpn-server-ip>",
891            ],
892            dig_deeper: Some("vpn"),
893        },
894    },
895
896    // ── Screen flickering / display issues ───────────────────────────────────
897    RecipeEntry {
898        triggers: &[
899            "display driver",
900            "refresh rate:",
901            "bits per pixel:",
902            "monitor:",
903            "screen flickering",
904            "display flickering",
905            "screen flashing",
906            "black screen",
907            "resolution wrong",
908            "wrong resolution",
909        ],
910        recipe: Recipe {
911            severity: "INVESTIGATE",
912            title: "Screen flickering or display issues",
913            steps: &[
914                "Update or roll back the display driver: Device Manager → Display Adapters → right-click GPU → Update driver (or Roll Back Driver if flickering started after a recent update)",
915                "Check for a refresh rate mismatch: Settings → System → Display → Advanced display → verify the refresh rate matches what your monitor supports",
916                "Inspect the cable: reseat or replace the HDMI/DisplayPort cable — a loose or failing cable is the most common cause of flickering",
917                "Check if Task Manager flickers when you open it (Win+X → Task Manager): if Task Manager does NOT flicker, the cause is a software/app conflict, not the driver",
918                "Disable hardware acceleration in Chrome/Edge: Settings → System → turn off 'Use hardware acceleration when available', then restart the browser",
919                "Run hematite --inspect device_health to check for GPU or display adapter PnP errors (yellow bangs in Device Manager)",
920                "If using NVIDIA: open NVIDIA Control Panel → Manage 3D Settings → verify G-Sync/VRR is correctly enabled or disabled for your panel type",
921                "For laptops: test on an external monitor — if the external is stable, the laptop panel or cable is the likely failure point",
922            ],
923            dig_deeper: Some("display_config"),
924        },
925    },
926
927    // ── Microphone not working ────────────────────────────────────────────────
928    RecipeEntry {
929        triggers: &[
930            "no recording endpoints found",
931            "recording endpoint",
932            "microphone privacy",
933            "microphone access: denied",
934            "input device",
935            "microphone not working",
936            "mic not working",
937            "mic not detected",
938            "microphone blocked",
939        ],
940        recipe: Recipe {
941            severity: "ACTION",
942            title: "Microphone not working or not detected",
943            steps: &[
944                "Check the privacy setting: Settings → Privacy & security → Microphone → ensure 'Microphone access' is On and your app has permission",
945                "Set the correct default input device: right-click the speaker icon in the taskbar → Sound settings → Input → choose the correct microphone",
946                "Restart the Windows Audio service: PowerShell (admin) → Restart-Service Audiosrv -Force; Restart-Service AudioEndpointBuilder -Force",
947                "Check the microphone is not muted at the hardware level — look for a physical mute button on the mic, headset, or laptop keyboard (Fn+F-key)",
948                "Run hematite --inspect audio to see which recording endpoints Windows has found and their current state",
949                "Update the audio driver: Device Manager → Sound, video and game controllers → right-click the audio device → Update driver",
950                "For a USB microphone: unplug, wait 10 seconds, replug — Windows re-enumerates the device and may fix a bad enumeration state",
951                "If the microphone works in one app but not another (e.g. works in Voice Recorder but not Teams): check the app's own audio settings, not Windows settings",
952            ],
953            dig_deeper: Some("audio"),
954        },
955    },
956
957    // ── Login / PIN / Windows Hello not working ───────────────────────────────
958    RecipeEntry {
959        triggers: &[
960            "wbiosrvc",
961            "biometric service",
962            "windows hello",
963            "pin not working",
964            "fingerprint not working",
965            "logon failure",
966            "event id 4625",
967            "failed logon",
968            "credential provider",
969            "sign-in failed",
970            "can't sign in",
971        ],
972        recipe: Recipe {
973            severity: "ACTION",
974            title: "Login, PIN, or Windows Hello not working",
975            steps: &[
976                "Reset the PIN: on the sign-in screen, click 'I forgot my PIN' — Windows will verify your Microsoft account or Azure AD identity and let you set a new PIN without needing the old one",
977                "Restart the Windows Biometric Service: PowerShell (admin) → Restart-Service WbioSrvc -Force",
978                "If fingerprint or face recognition stopped working: Settings → Accounts → Sign-in options → remove and re-enroll the biometric credential",
979                "For 'something went wrong' on the PIN screen: Settings → Accounts → Sign-in options → PIN (Windows Hello) → I forgot my PIN",
980                "Check for account lockout (repeated failed logins): run hematite --inspect sign_in and look for Event ID 4625 (failed logon) frequency",
981                "If the machine is domain-joined and the domain controller is unreachable, Windows may not accept domain credentials — use a local admin account as a fallback",
982                "If all sign-in methods fail at the lock screen, boot to Windows Recovery (hold Shift while clicking Restart) → Troubleshoot → Reset this PC as a last resort",
983            ],
984            dig_deeper: Some("sign_in"),
985        },
986    },
987
988    // ── High disk I/O — disk at 100% ─────────────────────────────────────────
989    RecipeEntry {
990        triggers: &[
991            "disk queue length:",
992            "average disk queue",
993            "disk i/o",
994            "high disk usage",
995            "disk at 100",
996            "100% disk",
997            "disk thrashing",
998            "disk saturation",
999        ],
1000        recipe: Recipe {
1001            severity: "INVESTIGATE",
1002            title: "Disk at 100% — high disk I/O",
1003            steps: &[
1004                "Identify the process driving disk I/O: run hematite --inspect processes and look at the R/W column for the top consumer",
1005                "Common culprits: Windows Search indexer (SearchIndexer.exe), Windows Update (TiWorker.exe, WUDFHost.exe), Antivirus scan, or a runaway backup job",
1006                "If Windows Search is the cause: Settings → Search → Windows Search → Indexing Options → Pause indexing for 15 minutes and see if disk drops",
1007                "If Windows Update (TiWorker.exe): let it finish — fighting a mid-update installation makes things worse; check Windows Update status in Settings",
1008                "Check for a failing drive: run hematite --inspect disk_health — a drive with SMART errors can cause 100% disk usage as the OS retries failing sectors",
1009                "Disable Windows Superfetch/SysMain if you have an SSD: PowerShell (admin) → Stop-Service SysMain; Set-Service SysMain -StartupType Disabled",
1010                "Check the page file: if RAM is full, Windows pages to disk constantly — run hematite --inspect pagefile and resource_load to verify",
1011                "For persistent 100% disk on an HDD: consider upgrading to an SSD — HDDs cannot sustain the I/O demand of modern Windows workloads",
1012            ],
1013            dig_deeper: Some("processes"),
1014        },
1015    },
1016
1017    // ── USB device not recognized ─────────────────────────────────────────────
1018    RecipeEntry {
1019        triggers: &[
1020            "[err:",
1021            "usb device not recognized",
1022            "unknown usb device",
1023            "device descriptor request failed",
1024            "usb not working",
1025            "usb port not working",
1026        ],
1027        recipe: Recipe {
1028            severity: "ACTION",
1029            title: "USB device not recognized or not working",
1030            steps: &[
1031                "Unplug the USB device, wait 10 seconds, then replug it — Windows re-enumerates the USB controller and often clears a bad enumeration state",
1032                "Try a different USB port — especially try a USB 2.0 port if the device was on a USB 3.0 port (some older accessories have compatibility issues with 3.x)",
1033                "Restart the USB Host Controller: Device Manager → Universal Serial Bus controllers → right-click each 'USB Root Hub' → Disable device, then Enable device",
1034                "Run the USB troubleshooter: Settings → System → Troubleshoot → Other troubleshooters → USB",
1035                "Check for driver errors: run hematite --inspect device_health to see PnP error codes — Error Code 43 is an unrecoverable device error requiring a driver reinstall or device replacement",
1036                "Update chipset and USB controller drivers: go to your motherboard manufacturer's website (ASUS, MSI, Gigabyte, ASRock) and download the latest chipset driver for your platform",
1037                "If the device worked before: uninstall the device in Device Manager (right-click → Uninstall device, check 'Delete the driver software'), then replug so Windows installs a fresh driver",
1038                "For persistent issues on all ports: run hematite --inspect device_health and check if 'USB Root Hub' itself shows an error — this points to a chipset/driver issue, not the accessory",
1039            ],
1040            dig_deeper: Some("device_health"),
1041        },
1042    },
1043
1044    // ── No Wi-Fi networks visible / can't find networks ───────────────────────
1045    RecipeEntry {
1046        triggers: &[
1047            "there is no wireless interface",
1048            "no wireless interface detected",
1049            "no wi-fi devices found",
1050            "wi-fi adapter disconnected",
1051            "no wireless tool available",
1052            "no wireless networks",
1053            "wifi adapter off",
1054            "wireless adapter disabled",
1055            "airplane mode",
1056        ],
1057        recipe: Recipe {
1058            severity: "ACTION",
1059            title: "No Wi-Fi networks visible — adapter or driver issue",
1060            steps: &[
1061                "Make sure the Wi-Fi adapter is on: Action Center (bottom-right) → click the Wi-Fi tile to toggle it on; also check the physical Wi-Fi key (Fn+F-key) on laptops",
1062                "Check Airplane Mode is off: Settings → Network & Internet → Airplane mode → Off",
1063                "Restart WLAN AutoConfig service: PowerShell (admin) → Restart-Service Wlansvc -Force",
1064                "Toggle the adapter off and on: Device Manager → Network Adapters → right-click the Wi-Fi adapter → Disable device, wait 5 seconds, Enable device",
1065                "Update the Wi-Fi driver: Device Manager → Network Adapters → right-click the Wi-Fi adapter → Update driver → Search automatically",
1066                "If no Wi-Fi adapter appears in Device Manager: run hematite --inspect device_health and look for hidden or missing network devices; the adapter may need a driver install from the laptop/motherboard manufacturer",
1067                "For Intel Wi-Fi adapters: download the latest driver from Intel's website (search 'Intel Wi-Fi driver') — the Windows generic driver sometimes loses network scan capability after Windows updates",
1068                "Reset network settings as a last resort: PowerShell (admin) → netsh wlan delete profile name=* then restart; this removes all saved Wi-Fi profiles but often fixes a corrupt wireless profile store",
1069            ],
1070            dig_deeper: Some("wifi"),
1071        },
1072    },
1073
1074    // ── Network share not accessible ─────────────────────────────────────────
1075    RecipeEntry {
1076        triggers: &[
1077            "server unreachable (ping failed)",
1078            "reachable:false",
1079            "share not accessible",
1080            "network path not found",
1081            "access is denied",
1082            "share access failed",
1083            "smb share unreachable",
1084            "network share",
1085        ],
1086        recipe: Recipe {
1087            severity: "INVESTIGATE",
1088            title: "Network share or mapped drive not accessible",
1089            steps: &[
1090                "Verify basic connectivity to the server: PowerShell → Test-NetConnection -ComputerName <server> -Port 445 (SMB port) — if this fails, the issue is network-level, not share-level",
1091                "Check the server is online: ping <server-name-or-IP> from PowerShell",
1092                "Confirm the share name is correct: Net View \\\\<server> lists all shares exposed by the server",
1093                "Re-enter credentials: Windows Credential Manager may have stale credentials — Win+R → control keymgr.dll → remove entries for the target server, then retry the share",
1094                "If 'Access Denied': verify your account has been granted share and NTFS permissions on the server side — contact the server admin to confirm your permissions",
1095                "Check SMB1 is not required: some old NAS devices only speak SMB1. Run hematite --inspect shares to see if SMB1 is disabled locally — if so, either enable it (security risk) or update the NAS firmware",
1096                "For domain environments: if the server name resolves but SMB fails, check if Kerberos is working — run hematite --inspect domain_health to verify DC reachability and GPO refresh",
1097                "Map the drive manually to force a fresh credential prompt: File Explorer → This PC → Map network drive → \\\\<server>\\<share> → check 'Connect using different credentials'",
1098            ],
1099            dig_deeper: Some("share_access"),
1100        },
1101    },
1102
1103    // ── Microsoft Store / AppX not working ───────────────────────────────────
1104    RecipeEntry {
1105        triggers: &[
1106            "microsoft.windowsstore | status: missing",
1107            "appx |",
1108            "desktopappinstaller | status: missing",
1109            "store is not responding",
1110            "wsreset",
1111            "appx package",
1112            "microsoft store not opening",
1113            "microsoft store not working",
1114            "store app not installing",
1115        ],
1116        recipe: Recipe {
1117            severity: "ACTION",
1118            title: "Microsoft Store or AppX apps not working",
1119            steps: &[
1120                "Reset the Store cache: open Run (Win+R) → type wsreset.exe → Enter. A blank window opens, waits ~30 seconds, then the Store launches automatically. This clears the Store cache without removing app data.",
1121                "If the Store still won't open: PowerShell (admin) → Get-AppXPackage -AllUsers Microsoft.WindowsStore | Foreach {Add-AppxPackage -DisableDevelopmentMode -Register \"$($_.InstallLocation)\\AppXManifest.xml\"} to re-register the Store package",
1122                "Ensure the Windows Update service is running: PowerShell (admin) → Start-Service wuauserv — the Store update pipeline depends on Windows Update",
1123                "Check AppX installer services: run hematite --inspect installer_health to see if AppX or Store services are in a failed state",
1124                "For 'This app can't open' errors: Settings → Apps → Apps & features → find the app → Advanced options → Reset — this wipes the app's local data but usually fixes launch failures",
1125                "If apps fail to install from the Store with error code 0x80073D02 or 0x80073CF9: run: PowerShell (admin) → Get-AppXPackage -AllUsers | Foreach {Add-AppxPackage -DisableDevelopmentMode -Register \"$($_.InstallLocation)\\AppXManifest.xml\"} to rebuild the full AppX registration",
1126                "For persistent Store issues: Settings → System → Troubleshoot → Other troubleshooters → Windows Store Apps",
1127            ],
1128            dig_deeper: Some("installer_health"),
1129        },
1130    },
1131
1132    // ── Sleep / hibernate / wake issues ──────────────────────────────────────
1133    RecipeEntry {
1134        triggers: &[
1135            "kernel-power",
1136            "power-troubleshooter",
1137            "sleep issue",
1138            "hibernate fail",
1139            "won't wake",
1140            "stuck after sleep",
1141            "wake after sleep",
1142            "sleep fail",
1143            "hibernate issue",
1144            "fast startup",
1145            "s0 low power idle",
1146            "system restart is pending",
1147            "pending file rename operations",
1148        ],
1149        recipe: Recipe {
1150            severity: "INVESTIGATE",
1151            title: "PC won't sleep, hibernate, or wake properly",
1152            steps: &[
1153                "Check what's preventing sleep: open PowerShell (admin) → powercfg /requests — shows apps or services actively blocking sleep",
1154                "See what woke the PC last time: PowerShell (admin) → powercfg /lastwake — identifies the wake source (scheduled task, device, network adapter)",
1155                "Check for wake timers: PowerShell (admin) → powercfg /waketimers — lists all wake timers; if unexpected, disable individual timers in Task Scheduler",
1156                "Disable Fast Startup (common cause of wake/sleep issues): Settings → System → Power & sleep → Additional power settings → Choose what the power buttons do → uncheck 'Turn on fast startup'",
1157                "Disable wake-on-LAN if not needed: Device Manager → Network Adapters → right-click NIC → Properties → Power Management → uncheck 'Allow this device to wake the computer'",
1158                "If the PC wakes immediately after sleeping: a device (USB hub, mouse, keyboard) is triggering wakeup — run PowerShell (admin) → powercfg /devicequery wake_armed to see which device is responsible, then disable its wake permission in Device Manager → Properties → Power Management",
1159                "For hibernate-specific issues: PowerShell (admin) → powercfg /h on to ensure hibernation is enabled; or /h off to disable it if you don't use hibernate",
1160                "Run the Power troubleshooter: Settings → System → Troubleshoot → Other troubleshooters → Power",
1161            ],
1162            dig_deeper: Some("log_check"),
1163        },
1164    },
1165
1166    // ── Keyboard, mouse, or touchpad not working ──────────────────────────────
1167    RecipeEntry {
1168        triggers: &[
1169            "hid keyboard",
1170            "hid mouse",
1171            "hid-compliant",
1172            "no hid devices",
1173            "input device",
1174            "keyboard not detected",
1175            "mouse not detected",
1176            "touchpad not working",
1177            "trackpad not working",
1178            "keyboard frozen",
1179            "mouse frozen",
1180        ],
1181        recipe: Recipe {
1182            severity: "ACTION",
1183            title: "Keyboard, mouse, or touchpad not working",
1184            steps: &[
1185                "For USB keyboards/mice: unplug, wait 10 seconds, replug into a different USB port — try a USB 2.0 port (blue or black) if it was in a USB 3.0 port (blue-interior)",
1186                "For wireless keyboards/mice: replace the battery and re-pair the USB receiver (unplug receiver, replug, hold the pairing button on the device)",
1187                "Restart the HID (Human Interface Device) service: PowerShell (admin) → Restart-Service hidserv -Force",
1188                "Check for driver errors: run hematite --inspect device_health — look for keyboard or mouse devices showing error codes in Device Manager",
1189                "Roll back or update the keyboard/mouse driver: Device Manager → Keyboards or Mice and other pointing devices → right-click → Update driver or Roll Back Driver",
1190                "For laptop touchpad: check if a keyboard shortcut disabled it (usually Fn+F7 or a dedicated touchpad key) — press it to toggle",
1191                "If touchpad disappears after Windows Update: check the laptop manufacturer's website (Dell, HP, Lenovo, ASUS) for a touchpad driver update — generic Windows HID drivers often lose touchpad gestures",
1192                "Run the Hardware troubleshooter: PowerShell (admin) → msdt.exe -id DeviceDiagnostic",
1193            ],
1194            dig_deeper: Some("peripherals"),
1195        },
1196    },
1197
1198    // ── High network usage — bandwidth hog ───────────────────────────────────
1199    RecipeEntry {
1200        triggers: &[
1201            "bytes sent (mb):",
1202            "bytes received (mb):",
1203            "rx errors:",
1204            "tx errors:",
1205            "high bandwidth",
1206            "bandwidth usage",
1207            "network saturation",
1208            "network usage high",
1209            "upload high",
1210            "download high",
1211        ],
1212        recipe: Recipe {
1213            severity: "INVESTIGATE",
1214            title: "High network usage — something is using all the bandwidth",
1215            steps: &[
1216                "Identify the process: run hematite --inspect processes to see CPU/RAM, then hematite --inspect connections to see which process has the most active TCP connections",
1217                "Check for Windows Update downloading in the background: Settings → Windows Update — if an update is downloading, let it finish or schedule it for off-hours",
1218                "Check for OneDrive or backup software syncing: OneDrive icon in taskbar → pause sync temporarily to test; same for Dropbox, Google Drive, Backup",
1219                "Check for delivery optimization (Windows Update peer sharing): Settings → Windows Update → Advanced options → Delivery Optimization → turn off 'Allow downloads from other PCs'",
1220                "Look for malware/miners: high sustained upload to unknown IPs is a red flag — run hematite --inspect security to check Defender status, then run a full Defender scan",
1221                "Use Resource Monitor for real-time drill-down: Task Manager → Performance tab → Open Resource Monitor → Network tab → shows per-process bytes/sec in real time",
1222                "For gaming: check for background game patching (Steam, Epic, Xbox) — pause downloads in each game client's settings",
1223                "If a browser is the top consumer: extensions can cause heavy network usage — try a private/incognito window to isolate, then disable extensions one at a time",
1224            ],
1225            dig_deeper: Some("network_stats"),
1226        },
1227    },
1228
1229    // ── Audio crackling / distortion / stuttering ─────────────────────────────
1230    RecipeEntry {
1231        triggers: &[
1232            "crackling",
1233            "audio distortion",
1234            "audio stuttering",
1235            "audio popping",
1236            "audio drops out",
1237            "audio cutting",
1238            "audio lag",
1239            "sound crackling",
1240            "sound distortion",
1241            "sound stuttering",
1242            "audio glitch",
1243            "dpc latency",
1244            "exclusive mode",
1245        ],
1246        recipe: Recipe {
1247            severity: "INVESTIGATE",
1248            title: "Audio crackling, distortion, or stuttering",
1249            steps: &[
1250                "Check the sample rate and bit depth: right-click the speaker icon → Sound settings → More sound settings → Playback tab → right-click your speaker → Properties → Advanced → set to '24 bit, 48000 Hz (Studio Quality)' or match your headset's spec",
1251                "Disable audio enhancements: in the same Advanced tab → uncheck 'Enable audio enhancements' — Realtek/DTS/Nahimic enhancements are a common cause of crackling",
1252                "Update or reinstall the audio driver: Device Manager → Sound, video and game controllers → right-click your audio device → Update driver; or download the latest from your motherboard/laptop manufacturer (Realtek, IDT, Intel SST)",
1253                "Check DPC (Deferred Procedure Call) latency: download LatencyMon (free, latencymon.com) and run it while crackling occurs — identifies which driver or service is causing audio buffer underruns",
1254                "Disable exclusive mode: right-click speaker → Properties → Advanced → uncheck 'Allow applications to take exclusive control of this device' — prevents apps like Spotify or Discord from monopolizing the audio device",
1255                "For Bluetooth audio crackling: increase Bluetooth quality by switching codec — in Bluetooth settings, ensure the device is using aptX or AAC instead of SBC; also move the device closer to reduce interference",
1256                "Disable power management on the audio adapter: Device Manager → Sound → right-click audio device → Properties → Power Management → uncheck 'Allow the computer to turn off this device to save power'",
1257                "If crackling started after a Windows Update: Device Manager → right-click audio device → Roll Back Driver to the previous version",
1258            ],
1259            dig_deeper: Some("audio"),
1260        },
1261    },
1262
1263    // ── Browser slow, crashing, or not working ────────────────────────────────
1264    RecipeEntry {
1265        triggers: &[
1266            "browser crash",
1267            "webview2 runtime: missing",
1268            "webview2: not installed",
1269            "browser slow",
1270            "browser freezing",
1271            "browser not responding",
1272            "browser high cpu",
1273            "chrome slow",
1274            "edge slow",
1275            "firefox slow",
1276            "browser not opening",
1277            "browser crashing",
1278            "browser keeps closing",
1279        ],
1280        recipe: Recipe {
1281            severity: "INVESTIGATE",
1282            title: "Browser slow, crashing, or not opening",
1283            steps: &[
1284                "Clear the browser cache: in Chrome/Edge → Ctrl+Shift+Delete → check 'Cached images and files' → All time → Clear data; this is the single most effective fix for browser slowness",
1285                "Test in a private/incognito window (Ctrl+Shift+N in Chrome/Edge, Ctrl+Shift+P in Firefox) — if the browser is fast in private mode, an extension is the cause",
1286                "Disable extensions one at a time: browser menu → Extensions (or Add-ons) → disable all, then re-enable one at a time to isolate the problem extension",
1287                "Disable hardware acceleration if the browser is crashing or displaying glitches: Chrome/Edge → Settings → System → turn off 'Use hardware acceleration when available' → relaunch",
1288                "Reset the browser profile as a last resort: Chrome → chrome://settings/resetProfileSettings; Edge → edge://settings/resetProfileSettings — this removes extensions and preferences but keeps bookmarks",
1289                "For 'browser not opening' after a Windows update: run hematite --inspect browser_health to check for WebView2 runtime issues; reinstall WebView2 from microsoft.com/en-us/edge/webview2 if it is missing",
1290                "Update the browser: Chrome/Edge → menu (⋮) → Help → About → it will auto-update if behind; Firefox → menu → Help → About Firefox",
1291                "Check if a browser policy is locking settings: run hematite --inspect browser_health and look for 'Policy:' entries — corporate or malware-deployed policies can prevent changes and cause slowness",
1292            ],
1293            dig_deeper: Some("browser_health"),
1294        },
1295    },
1296
1297    // ── Slow startup / PC takes a long time to boot ───────────────────────────
1298    RecipeEntry {
1299        triggers: &[
1300            "startup takes", "long boot time", "slow to start up", "boot is slow",
1301            "computer is slow to start", "windows loads slowly", "pc takes forever to boot",
1302            "startup items high impact", "many startup programs", "takes forever to start",
1303            "slow to boot up", "boot time is",
1304        ],
1305        recipe: Recipe {
1306            severity: "INVESTIGATE",
1307            title: "Windows startup is slow — PC takes a long time to boot",
1308            steps: &[
1309                "Open Task Manager (Ctrl+Shift+Esc) → Startup tab → disable 'High impact' programs you don't need at login (RGB controllers, game launchers, chat apps are common culprits)",
1310                "Run hematite --inspect startup_items to see the full list of programs set to run at login",
1311                "Disable Fast Startup if the machine feels slow or unreliable after restarting: Settings → System → Power & sleep → Additional power settings → Choose what the power buttons do → uncheck 'Turn on fast startup' → Save changes",
1312                "Run an SFC scan if slow boot started suddenly (corrupted system files delay boot): PowerShell (admin) → sfc /scannow — then DISM /Online /Cleanup-Image /RestoreHealth to repair the component store",
1313                "Check disk health — a failing or near-full HDD is the most common hidden cause of slow boot: run hematite --inspect disk_health and hematite --inspect storage",
1314                "Check recent Windows Updates: Settings → Windows Update → View update history — if slowness started after a specific update, note the KB number and consider rolling it back (Settings → Update history → Uninstall updates)",
1315                "Check for malware that runs at startup: run hematite --inspect security then a full Defender scan — malware processes running at boot significantly extend startup time",
1316                "For laptops: check the active power plan (Settings → Power & sleep → Additional power settings) — 'Power saver' throttles boot-time CPU speed; switch to 'Balanced' or 'High performance'",
1317            ],
1318            dig_deeper: Some("startup_items"),
1319        },
1320    },
1321
1322    // ── Windows Update stuck downloading or failing ───────────────────────────
1323    RecipeEntry {
1324        // Note: "windows update" is already owned by the "updates pending" recipe (shorter AC pattern).
1325        // Use error-code patterns and stuck-download phrases that are unambiguous.
1326        triggers: &[
1327            "update error 0x", "0x8024a105", "0x800705b4", "0x80070422", "0x8024",
1328            "update failed to install", "update stuck downloading", "update downloading at 0%",
1329            "update keeps failing", "cumulative update failed", "feature update failed",
1330            "update rollback failed", "failed to configure windows updates",
1331            "update install failed",
1332        ],
1333        recipe: Recipe {
1334            severity: "ACTION",
1335            title: "Windows Update stuck downloading or failing with an error code",
1336            steps: &[
1337                "Run the built-in troubleshooter first: Settings → Update & Security → Troubleshoot → Additional troubleshooters → Windows Update → Run the troubleshooter — it fixes most common stuck states automatically",
1338                "Reset the Windows Update cache (most effective fix for stuck downloads): PowerShell (admin) → net stop wuauserv → net stop bits → Remove-Item C:\\Windows\\SoftwareDistribution\\* -Recurse -Force → net start wuauserv → net start bits — then check for updates again",
1339                "Free up disk space — updates require 10+ GB free: run hematite --inspect storage; if space is low, empty the Recycle Bin and run Disk Cleanup (cleanmgr.exe) before retrying",
1340                "Repair the Windows component store if DISM errors appear: PowerShell (admin) → DISM /Online /Cleanup-Image /RestoreHealth (takes 5–15 min) → then sfc /scannow → then retry the update",
1341                "For error 0x80070422 (service disabled): Services (services.msc) → find 'Windows Update' → right-click → Properties → set Startup type to 'Automatic' → click Start",
1342                "For error 0x8024a105 or updates stuck at 0%: restart the update services — run hematite --fix 'Windows Update broken' for the automated service restart steps",
1343                "Manually install stuck updates: go to Settings → Windows Update → View update history → note the failing KB number → download it from catalog.update.microsoft.com and run the installer directly",
1344                "If nothing works: the Windows Update Reset Script resets all update components — download from microsoft.com/en-us/download/details.aspx?id=25232 and run as Administrator",
1345            ],
1346            dig_deeper: Some("updates"),
1347        },
1348    },
1349
1350    // ── Access denied / file permission error ─────────────────────────────────
1351    // Note: "access is denied" is owned by the Network share recipe (shorter AC pattern wins
1352    // when patterns share the same trigger string). Use permission-specific phrases here.
1353    RecipeEntry {
1354        triggers: &[
1355            "access denied", "you don't have permission",
1356            "you do not have permission", "cannot access this folder",
1357            "permission denied", "unable to access",
1358            "folder access denied", "unauthorized access",
1359        ],
1360        recipe: Recipe {
1361            severity: "INVESTIGATE",
1362            title: "Access denied — file or folder permission error",
1363            steps: &[
1364                "Try running the application as Administrator: right-click the app → Run as administrator — many system folders require elevated permissions",
1365                "Take ownership of the file or folder: right-click the file/folder → Properties → Security tab → Advanced → Owner → Edit → change owner to your account → check 'Replace owner on subcontainers and objects' → OK",
1366                "Grant your account full control: Properties → Security → Edit → Add → type your username → check Full Control → OK; then retry the operation",
1367                "For files on an external drive or from another PC: the old security identifier (SID) from the previous system owns the files — take ownership as above; alternatively, right-click the drive root and grant your account permissions there",
1368                "For network shares: check that your account has read/write permissions on the share — contact the share owner or IT admin to verify permissions",
1369                "For C:\\Windows or system folders: these are locked by design for security; use the built-in Administrator account (net user administrator /active:yes in an elevated command prompt) only if absolutely necessary",
1370                "Check if the file is in use by another process: Task Manager (Ctrl+Shift+Esc) → Details tab → right-click → End task for the process holding the file; or use Sysinternals Process Explorer → Find Handle to locate which process has the lock",
1371                "Run hematite --inspect user_accounts to verify your account type — Standard users cannot access certain folders that Administrators can",
1372            ],
1373            dig_deeper: Some("user_accounts"),
1374        },
1375    },
1376
1377    // ── Wi-Fi keeps disconnecting or dropping ────────────────────────────────
1378    RecipeEntry {
1379        triggers: &[
1380            "wifi disconnects", "wifi keeps dropping", "wifi keeps disconnecting",
1381            "internet keeps cutting out", "wifi unstable", "wifi drops every",
1382            "internet drops", "connection drops", "wifi connection drops",
1383            "network keeps disconnecting", "wifi intermittent",
1384        ],
1385        recipe: Recipe {
1386            severity: "INVESTIGATE",
1387            title: "Wi-Fi keeps disconnecting or dropping intermittently",
1388            steps: &[
1389                "Run hematite --inspect wifi to check signal strength and negotiated speed — a weak signal (RSSI worse than -70 dBm) is the most common cause of intermittent drops; move closer to the router or use a Wi-Fi extender",
1390                "Disable Wi-Fi adapter power management (the #1 fix for random disconnects): Device Manager → Network Adapters → right-click Wi-Fi adapter → Properties → Power Management → uncheck 'Allow the computer to turn off this device to save power'",
1391                "Update the Wi-Fi driver: Device Manager → Network Adapters → right-click Wi-Fi adapter → Update driver; or download the latest driver from the laptop/motherboard manufacturer's website",
1392                "Forget and rejoin the network: Settings → Network & Internet → Wi-Fi → Manage known networks → select the network → Forget → reconnect and re-enter the password",
1393                "Change the DNS server to a more reliable provider: Settings → Network & Internet → Wi-Fi → Hardware properties → DNS server assignment → Manual → IPv4: 1.1.1.1 and 1.0.0.1 (Cloudflare) or 8.8.8.8 and 8.8.4.4 (Google)",
1394                "Check for channel congestion: router admin page (usually 192.168.1.1) → change the Wi-Fi channel to a less congested one (1, 6, or 11 for 2.4 GHz; any non-overlapping channel for 5 GHz)",
1395                "Reset the TCP/IP stack if drops are accompanied by complete connectivity loss: PowerShell (admin) → netsh int ip reset → netsh winsock reset → reboot",
1396                "Check router firmware updates — outdated router firmware causes intermittent disconnections; update via the router's admin interface",
1397            ],
1398            dig_deeper: Some("wifi"),
1399        },
1400    },
1401
1402    // ── GPU / display driver crash (TDR failure, nvlddmkm.sys, black screen) ──
1403    // Note: "display driver" is owned by the Screen flickering recipe. Use unique
1404    // driver-crash identifiers only: kernel fault names, TDR codes, GPU-specific terms.
1405    RecipeEntry {
1406        triggers: &[
1407            "nvlddmkm.sys", "nvlddmkm", "amdkmdag.sys", "amdkmdag", "atikmpag.sys",
1408            "dxgkrnl.sys", "tdr failure", "video_tdr_failure", "gpu driver crash",
1409            "gpu driver stopped", "graphics driver stopped", "video hardware error",
1410            "gpu hang", "display adapter error code 43",
1411        ],
1412        recipe: Recipe {
1413            severity: "ACTION",
1414            title: "GPU or display driver crash — TDR failure or black screen",
1415            steps: &[
1416                "Run hematite --inspect device_health to check if the GPU shows a yellow bang in Device Manager (error code 43 = driver failure; error code 45 = device not connected at last boot)",
1417                "Check crash events: run hematite --inspect recent_crashes and look for VIDEO_TDR_FAILURE, nvlddmkm.sys (NVIDIA), or amdkmdag.sys (AMD) in the BSOD list — confirms this is a driver-level crash",
1418                "Update the GPU driver: NVIDIA → download GeForce Experience or go to nvidia.com/drivers; AMD → Radeon Software or amd.com/support — always use the GPU manufacturer's installer, not Windows Update's driver",
1419                "If the crash started after a driver update: roll back the driver — Device Manager → Display Adapters → right-click GPU → Properties → Driver tab → Roll Back Driver",
1420                "For a clean driver reinstall using DDU: boot into Safe Mode (hold Shift + Restart → Troubleshoot → Advanced options → Startup Settings → F4), download Display Driver Uninstaller from guru3d.com, run DDU → Clean and restart, then install the latest driver fresh",
1421                "Check for overheating: run hematite --inspect thermal — GPU TDR failures often happen when the GPU exceeds 90°C; clean the GPU heatsink and reapply thermal paste if temperatures are consistently high",
1422                "Check for unstable overclock: if GPU core clock or VRAM frequency is overclocked via MSI Afterburner or AMD Wattman, restore default clocks — even a small instability at high load can cause TDR crashes",
1423                "As a temporary diagnostic step, raise the TDR delay to confirm TDR is the cause (not a fix): PowerShell (admin) → reg add HKLM\\System\\CurrentControlSet\\Control\\GraphicsDrivers /v TdrDelay /t REG_DWORD /d 8 /f — if crashes stop happening, a driver reinstall or cooling fix will resolve it permanently",
1424            ],
1425            dig_deeper: Some("device_health"),
1426        },
1427    },
1428
1429    // ── Antimalware Service Executable / Defender high CPU ───────────────────
1430    // Note: "malware" (7 chars) is owned by the Threat detected recipe — it's a substring of
1431    // "antimalware". Use "msmpeng" and process-name patterns only (not "antimalware service").
1432    RecipeEntry {
1433        triggers: &[
1434            "msmpeng.exe", "msmpeng", "wdnissvc.exe", "wdnissvc",
1435            "defender using high cpu", "defender scan high cpu", "mssense.exe high",
1436            "windows defender high", "defender cpu",
1437        ],
1438        recipe: Recipe {
1439            severity: "INVESTIGATE",
1440            title: "Antimalware Service Executable (MsMpEng.exe) using high CPU",
1441            steps: &[
1442                "Check if a scheduled scan is running: Task Manager (Ctrl+Shift+Esc) → Details → sort by CPU → if MsMpEng.exe is high, open Windows Security → Virus & threat protection → Current threats — let an active scan complete before taking action",
1443                "Exclude the Windows Temp folder and your main development/work directories from real-time scanning: Windows Security → Virus & threat protection → Virus & threat protection settings → Exclusions → Add an exclusion → Folder → add C:\\Windows\\Temp and your project folders",
1444                "Change the scheduled scan time to off-peak hours: Task Scheduler → Microsoft → Windows → Windows Defender → Windows Defender Scheduled Scan → Properties → Triggers → change the time to 3:00 AM or whenever you're not working",
1445                "Disable 'Sample submission' to reduce network-related CPU spikes: Windows Security → Virus & threat protection settings → Automatic sample submission → Off",
1446                "Run hematite --inspect resource_load to confirm MsMpEng is the top CPU consumer — sometimes it's a false lead and a different process (SearchIndexer, WSUS, Windows Update) is the actual culprit",
1447                "Run hematite --inspect security to verify Defender is fully updated — outdated signatures force a more exhaustive scan of each file, significantly increasing CPU usage",
1448                "If the issue is persistent and not during a scan: check for malware that is forcing Defender to constantly rescan itself — run a manual full scan (Windows Security → Full scan) from Safe Mode to clear any persistent threat",
1449                "As a last resort for workstations where Defender conflicts with enterprise AV: use Group Policy to disable MsMpEng real-time monitoring (not recommended on personal machines) — gpedit.msc → Computer Configuration → Administrative Templates → Windows Components → Microsoft Defender Antivirus → Turn off Microsoft Defender Antivirus → Enabled",
1450            ],
1451            dig_deeper: Some("resource_load"),
1452        },
1453    },
1454
1455    // ── External monitor not detected / no signal ─────────────────────────────
1456    // Note: "display adapter" is owned by the GPU crash recipe; use monitor-specific phrases.
1457    RecipeEntry {
1458        triggers: &[
1459            "monitor not detected", "second monitor not showing", "second monitor not detected",
1460            "hdmi not working", "displayport not detected", "display not detected",
1461            "external display not", "no signal on monitor", "monitor not recognized",
1462            "extend display not", "duplicate display not", "monitor shows no signal",
1463        ],
1464        recipe: Recipe {
1465            severity: "INVESTIGATE",
1466            title: "External monitor not detected or showing no signal",
1467            steps: &[
1468                "Press Win+P to open the display projection menu → choose 'Extend' or 'Duplicate' — Windows sometimes stops detecting secondary monitors after sleep or lock",
1469                "Unplug and replug the cable (HDMI or DisplayPort) at both ends — contact issues are the most common cause of 'no signal'; try a different cable if available",
1470                "Run hematite --inspect display_config to see which monitors Windows currently detects and their reported resolution and refresh rate",
1471                "In Display Settings (right-click desktop → Display settings) → scroll down → click 'Detect' under Multiple displays — forces Windows to re-scan for connected monitors",
1472                "Try a different port: if using HDMI 1, try HDMI 2 or the DisplayPort on the same GPU or dock",
1473                "Check if the monitor input source matches: most monitors have an on-screen menu to switch between HDMI 1, HDMI 2, DisplayPort, VGA — cycle through all inputs",
1474                "Update or reinstall the GPU driver: Device Manager → Display Adapters → right-click GPU → Update driver; or use GeForce Experience (NVIDIA) or Radeon Software (AMD) for the latest driver",
1475                "For docks and USB-C hubs: ensure the dock has a DisplayLink or Thunderbolt driver installed and is connected to a Thunderbolt-capable port; generic USB-C ports often don't support video output",
1476            ],
1477            dig_deeper: Some("display_config"),
1478        },
1479    },
1480
1481    // ── Windows Explorer / desktop / taskbar crashed ──────────────────────────
1482    // Note: "explorer" could be a substring — use "explorer.exe" or "explorer crash" to be precise.
1483    RecipeEntry {
1484        triggers: &[
1485            "explorer.exe crash", "explorer.exe not responding", "windows explorer crash",
1486            "file explorer crash", "desktop icons disappeared", "taskbar disappeared",
1487            "taskbar not responding", "start menu not working", "start menu crashed",
1488            "desktop froze", "desktop not responding", "shell infrastructure crash",
1489        ],
1490        recipe: Recipe {
1491            severity: "ACTION",
1492            title: "Windows Explorer / desktop or taskbar crashed",
1493            steps: &[
1494                "Restart Explorer immediately without rebooting: press Ctrl+Shift+Esc → Task Manager → Details tab → find explorer.exe → right-click → End task → File → Run new task → type explorer.exe → OK",
1495                "If Task Manager won't open: press Ctrl+Alt+Del → Task Manager → or run from the lock screen",
1496                "Check for recent Windows Updates or driver updates that may have caused the crash: Settings → Windows Update → View update history — note what installed in the last 48 hours",
1497                "Check the Application event log for crash details: run hematite --inspect log_check and look for 'Windows Explorer' or 'APPCRASH' events near the crash time",
1498                "Run SFC to repair corrupted shell files (a common cause of recurring Explorer crashes): PowerShell (admin) → sfc /scannow → then DISM /Online /Cleanup-Image /RestoreHealth → reboot",
1499                "If Desktop icons keep disappearing: right-click Desktop → View → ensure 'Show desktop icons' is checked",
1500                "If Start menu doesn't work after reboot: PowerShell (admin) → Get-AppXPackage -AllUsers | Foreach {Add-AppxPackage -DisableDevelopmentMode -Register \"$($_.InstallLocation)\\AppXManifest.xml\"} — reinstalls shell UWP components",
1501                "Create a new user profile as a diagnostic: Settings → Accounts → Family & other users → Add someone else — if Explorer works fine in the new profile, your current profile is corrupt",
1502            ],
1503            dig_deeper: Some("log_check"),
1504        },
1505    },
1506
1507    // ── Application crash history ─────────────────────────────────────────────
1508    RecipeEntry {
1509        triggers: &[
1510            "application crash event(s)",
1511            "application hang event(s)",
1512            "most-crashed application:",
1513            "wer reports archived",
1514            "faulting module:",
1515        ],
1516        recipe: Recipe {
1517            severity: "ACTION",
1518            title: "Application crash history detected",
1519            steps: &[
1520                "Identify the faulting app and module from the crash list above — the module name tells you whether it's a system DLL, a runtime, or an app-specific component",
1521                "If the faulting module is a system DLL (ntdll.dll, kernelbase.dll): run SFC and DISM — PowerShell (admin): sfc /scannow then DISM /Online /Cleanup-Image /RestoreHealth",
1522                "If the faulting module is a Visual C++ runtime (vcruntime*.dll, msvcp*.dll, vcomp*.dll): download and reinstall all Visual C++ Redistributable packages from microsoft.com/download",
1523                "For the most-crashed app: uninstall it (Settings → Apps), reboot, then reinstall a fresh copy from the publisher — cached corrupted app data often causes repeat crashes",
1524                "Try running the crashing app as Administrator: right-click its shortcut → Run as administrator — some apps need elevation to access resources they depend on",
1525                "Check for conflicting third-party AV or security software injecting into the process: temporarily disable real-time protection and test; if the crash stops, add the app folder to AV exclusions",
1526                "Check for pending Windows Updates: Settings → Windows Update → Check for updates — a pending cumulative update may patch the faulting module",
1527                "Run hematite --inspect recent_crashes to also check for BSOD events — kernel crashes near the same time as app crashes often share a root cause (bad driver, failing RAM)",
1528            ],
1529            dig_deeper: Some("app_crashes"),
1530        },
1531    },
1532
1533    // ── Outlook add-in resiliency / crash evidence ────────────────────────────
1534    RecipeEntry {
1535        triggers: &[
1536            "resiliencydisableditems:",
1537            "outlook's crash resiliency has auto-disabled",
1538            "active outlook add-ins detected — add-in overload",
1539            "add-in pressure, large ost files",
1540            "large ost files can cause outlook slowness",
1541            "application error |",
1542            "recent outlook crash evidence found",
1543        ],
1544        recipe: Recipe {
1545            severity: "ACTION",
1546            title: "Outlook add-in crash or OST / memory pressure",
1547            steps: &[
1548                "Start Outlook in safe mode to confirm add-ins are the cause: hold Ctrl and click the Outlook icon (or run: outlook.exe /safe) — if Outlook works fine in safe mode, an add-in is crashing it",
1549                "Identify and remove the crashing add-in: File → Options → Add-ins → Manage: COM Add-ins → Go → uncheck add-ins one at a time, restarting between each to find the culprit",
1550                "Clear resiliency-disabled add-ins from the registry: in PowerShell (admin) → Remove-Item 'HKCU:\\Software\\Microsoft\\Office\\16.0\\Outlook\\Resiliency\\DisabledItems' -Recurse -ErrorAction SilentlyContinue — then re-enable your needed add-ins",
1551                "If Outlook RAM is very high (1500+ MB): a large or corrupt OST file is the likely cause — in Outlook: File → Account Settings → Account Settings → Data Files → select the OST → open file location, close Outlook, rename the OST to .old, then reopen Outlook (it rebuilds from the server)",
1552                "Repair the Office installation: Control Panel → Programs → Microsoft 365 → Change → Quick Repair (fast), then Online Repair (thorough, requires internet) if Quick Repair doesn't fix it",
1553                "Check the Application event log for the faulting module: run hematite --inspect app_crashes and filter for OUTLOOK.EXE — this shows which DLL (often a third-party add-in or mso.dll) is faulting",
1554                "Update all Office add-ins: COM add-ins from vendors (Grammarly, Adobe, Zoom) often break after major Office updates — download the latest version of each from the vendor's website",
1555            ],
1556            dig_deeper: Some("outlook"),
1557        },
1558    },
1559];
1560
1561pub struct HealthScore {
1562    pub grade: char,
1563    pub label: &'static str,
1564    pub action_count: usize,
1565    pub investigate_count: usize,
1566    pub monitor_count: usize,
1567}
1568
1569impl HealthScore {
1570    pub fn summary_line(&self) -> String {
1571        match (
1572            self.action_count,
1573            self.investigate_count,
1574            self.monitor_count,
1575        ) {
1576            (0, 0, 0) => "No issues found — machine is healthy.".to_string(),
1577            (0, 0, m) => format!("{} item(s) to monitor.", m),
1578            (0, i, 0) => format!("{} item(s) need investigation.", i),
1579            (0, i, m) => format!("{} item(s) need investigation, {} to monitor.", i, m),
1580            (a, 0, 0) => format!("{} item(s) require immediate action.", a),
1581            (a, i, _) => format!(
1582                "{} item(s) require immediate action, {} need investigation.",
1583                a, i
1584            ),
1585        }
1586    }
1587
1588    /// A plain-English sentence for non-technical users that explains what
1589    /// the grade means and what to do next.
1590    pub fn grade_intro(&self) -> &'static str {
1591        match self.grade {
1592            'A' => "Your PC is in great shape — no issues were found. The diagnostic data below is included for reference.",
1593            'B' => "Your PC is doing well, but there's one thing worth a closer look. The action plan below has specific steps.",
1594            'C' => "Your PC needs some attention. A couple of things should be investigated — follow the action plan below.",
1595            'D' => "Your PC needs attention. There are issues that should be fixed — follow the action plan below.",
1596            _ => "Your PC has critical issues that need immediate attention. Work through the action plan below as soon as possible.",
1597        }
1598    }
1599}
1600
1601/// Compute a health grade (A–F) from diagnostic output sections.
1602pub fn score_health(outputs: &[(&str, &str)]) -> HealthScore {
1603    let all_recipes = collect_unique_recipes(outputs);
1604
1605    let action_count = all_recipes
1606        .iter()
1607        .filter(|r| r.severity == "ACTION")
1608        .count();
1609    let investigate_count = all_recipes
1610        .iter()
1611        .filter(|r| r.severity == "INVESTIGATE")
1612        .count();
1613    let monitor_count = all_recipes
1614        .iter()
1615        .filter(|r| r.severity == "MONITOR")
1616        .count();
1617
1618    let (grade, label) = if action_count >= 3 {
1619        ('F', "Critical")
1620    } else if action_count >= 1 {
1621        ('D', "Poor")
1622    } else if investigate_count >= 2 {
1623        ('C', "Fair")
1624    } else if investigate_count >= 1 {
1625        ('B', "Good")
1626    } else {
1627        ('A', "Excellent")
1628    };
1629
1630    HealthScore {
1631        grade,
1632        label,
1633        action_count,
1634        investigate_count,
1635        monitor_count,
1636    }
1637}
1638
1639/// Format all matching recipes for a given diagnostic output into a
1640/// human-readable action plan section suitable for a Markdown report.
1641pub fn format_action_plan(outputs: &[(&str, &str)]) -> String {
1642    let mut all_recipes = collect_unique_recipes(outputs);
1643
1644    if all_recipes.is_empty() {
1645        return "No actionable findings — machine appears healthy.\n".to_string();
1646    }
1647
1648    // Sort: ACTION first, then INVESTIGATE, then MONITOR
1649    all_recipes.sort_by_key(|r| match r.severity {
1650        "ACTION" => 0,
1651        "INVESTIGATE" => 1,
1652        _ => 2,
1653    });
1654
1655    let mut out = String::with_capacity(all_recipes.len() * 200);
1656    for (i, recipe) in all_recipes.iter().enumerate() {
1657        let badge = match recipe.severity {
1658            "ACTION" => "⚠ ACTION REQUIRED",
1659            "INVESTIGATE" => "🔍 INVESTIGATE",
1660            _ => "📊 MONITOR",
1661        };
1662        let _ = write!(out, "### {}. {} — {}\n\n", i + 1, badge, recipe.title);
1663        for step in recipe.steps {
1664            out.push_str("- ");
1665            out.push_str(step);
1666            out.push('\n');
1667        }
1668        if let Some(_topic) = recipe.dig_deeper {
1669            out.push_str("\n*Run `hematite --diagnose` for a deeper automated investigation.*\n");
1670        }
1671        out.push('\n');
1672    }
1673
1674    out
1675}
1676
1677/// Format all matching recipes as an HTML fragment for embedding in a report page.
1678pub fn format_action_plan_html(outputs: &[(&str, &str)]) -> String {
1679    let mut all_recipes = collect_unique_recipes(outputs);
1680
1681    if all_recipes.is_empty() {
1682        return "<p class=\"healthy\">No actionable findings — machine appears healthy.</p>\n"
1683            .to_string();
1684    }
1685
1686    all_recipes.sort_by_key(|r| match r.severity {
1687        "ACTION" => 0,
1688        "INVESTIGATE" => 1,
1689        _ => 2,
1690    });
1691
1692    let mut out = String::with_capacity(all_recipes.len() * 400);
1693    for (i, recipe) in all_recipes.iter().enumerate() {
1694        let (sev_class, badge_class, badge_text) = match recipe.severity {
1695            "ACTION" => ("sev-action", "b-action", "ACTION REQUIRED"),
1696            "INVESTIGATE" => ("sev-investigate", "b-investigate", "INVESTIGATE"),
1697            _ => ("sev-monitor", "b-monitor", "MONITOR"),
1698        };
1699        let _ = writeln!(out, "<div class=\"recipe {}\">", sev_class);
1700        let _ = writeln!(
1701            out,
1702            "<h3><span class=\"badge {}\">{}</span> {}. {}</h3>",
1703            badge_class,
1704            badge_text,
1705            i + 1,
1706            he(recipe.title)
1707        );
1708        out.push_str("<ol>\n");
1709        for step in recipe.steps {
1710            let _ = writeln!(out, "<li>{}</li>", he(step));
1711        }
1712        out.push_str("</ol>\n");
1713        if let Some(_topic) = recipe.dig_deeper {
1714            out.push_str(
1715                "<p class=\"dig-deeper\">Run <code>hematite --diagnose</code> for a deeper automated investigation of this issue.</p>\n"
1716            );
1717        }
1718        out.push_str("</div>\n");
1719    }
1720    out
1721}
1722
1723use crate::agent::html_template::he;