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