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

@ -6,7 +6,8 @@ import { cpSync, mkdtempSync, readFileSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { mint } from '../cli/lib/confirm-token';
import { FIX_METHODS, scanFixAction } from '../cli/lib/fix-actions';
import { AI_FIX_RULES, FIX_METHODS, SCAN_WRITE_RULES, scanFixAction } from '../cli/lib/fix-actions';
import { readFileSync as read } from 'node:fs';
import { runScan } from '../cli/lib/scan';
import { appleRules, copyrightRules, playRules } from '../cli/lib/validate-rules';
import { FORBIDDEN_ARGS, TOOLS, toArgv } from '../cli/mcp-tools';
@ -57,6 +58,16 @@ describe('finding -> fix action', () => {
});
});
describe('Fix with AI rules', () => {
test('only real scan rules that need a code change; never a compliance answer or a deterministic fix', () => {
const src = ['rules.ts', 'play-rules.ts'].map(f => read(join(import.meta.dir, '..', 'cli', 'lib', 'scan', f), 'utf8')).join('\n');
const ids = new Set([...src.matchAll(/rule: '([a-z0-9-]+)'/g)].map(m => m[1]!));
for (const r of AI_FIX_RULES) expect({ r, known: ids.has(r) }).toEqual({ r, known: true });
for (const r of ['export-compliance', 'demo-account', 'template-answers', 'privacy-labels', 'play-health-connect', 'metadata-keywords']) expect(AI_FIX_RULES.has(r)).toBe(false);
for (const r of SCAN_WRITE_RULES) expect(AI_FIX_RULES.has(r)).toBe(false);
});
});
describe('scan.write round trip', () => {
test('dry run shows the config diff and writes nothing; the confirmed run writes; the finding is gone', async () => {
const dir = mkdtempSync(join(tmpdir(), 'sb-fix-'));