desktop: Fix with AI on scan findings that need a code change (aiFix: true, AI_FIX_RULES). The host (src-tauri/src/ai_fix.rs) runs the owner's local Claude Code headless in the app folder with Read / Edit / Write / Grep / Glob only (Bash, web tools, sub-agents disallowed) and a prompt it builds from the finding's fields; it refuses while the folder has uncommitted files, streams progress, times out after 10 min with a heartbeat, kills the process group on Cancel or exit, and records the run's changed files so Undo restores exactly those; the window shows the diff with Keep / Undo and rescans. Without claude the button copies the prompt (Copy for AI). Only the claude binary, git and kill / taskkill are started there (tests/security.rs); tests/ai_fix.rs covers the prompt, dirty-tree refusal, undo scope, timeout and cancel

This commit is contained in:
CRTkafa 2026-10-15 06:26:29 +08:00
parent 992f5bcc0e
commit c95f2af8ff
24 changed files with 1023 additions and 10 deletions

View file

@ -179,6 +179,16 @@ fn sidecar_starts_with_fixed_arguments_from_one_place() {
assert!(src.contains("Command::new(&bin)\n .args(policy::SIDECAR_ARGS)"));
continue;
}
// Fix with AI: only the claude binary (claude_command checks its name), git, and kill / taskkill by PID
if p.ends_with("ai_fix.rs") {
for (i, _) in src.match_indices("Command::new(") {
let arg = &src[i + "Command::new(".len()..];
let ok = ["claude)", "\"git\")", "\"kill\")", "\"taskkill\")"];
assert!(ok.iter().any(|o| arg.starts_with(o)), "unexpected program: {}", &arg[..arg.find(')').unwrap_or(20).min(40)]);
}
assert!(src.contains("if !matches!(name, \"claude\" | \"claude.exe\" | \"claude.cmd\")"));
continue;
}
// other programs: only the download checks (unpack, signature, version banner), with fixed program names
if src.contains("Command::new(") {
assert!(p.ends_with("downloads.rs"), "{} starts a program", p.display());