export type CommandCategory = | "SAFE_READ" | "BUILD" | "TEST" | "LINT" | "INSTALL" | "DESTRUCTIVE" | "DEPLOY" | "DATABASE" | "NETWORK" | "UNKNOWN"; export type RiskLevel = "low" | "medium" | "high" | "critical"; export interface CommandClassification { category: CommandCategory; riskLevel: RiskLevel; reason: string; matchedRule: string; } interface RuleDef { id: string; category: CommandCategory; riskLevel: RiskLevel; reason: string; pattern: RegExp; } const RULES: RuleDef[] = [ { id: "safe-read-shell", category: "SAFE_READ", riskLevel: "low", reason: "Read-only shell/file inspection command.", pattern: /^(dir|ls|pwd|cat|type|more|get-content|get-childitem)\b/, }, { id: "safe-read-git", category: "SAFE_READ", riskLevel: "low", reason: "Read-only git inspection command.", pattern: /^git\s+(status|diff|show|log)\b/, }, { id: "build-js", category: "BUILD", riskLevel: "medium", reason: "Project build command.", pattern: /^(npm|pnpm|yarn|bun)\s+(run\s+)?build\b/, }, { id: "build-rust-go-desktop", category: "BUILD", riskLevel: "medium", reason: "Compile/package build command.", pattern: /^(cargo\s+build|wails\s+build|go\s+build)\b/, }, { id: "test-js", category: "TEST", riskLevel: "medium", reason: "Test execution command.", pattern: /^(npm|pnpm|yarn|bun)\s+(run\s+)?test\b/, }, { id: "test-go-rust-python", category: "TEST", riskLevel: "medium", reason: "Language test execution command.", pattern: /^(go\s+test|cargo\s+test|pytest|python\s+-m\s+pytest)\b/, }, { id: "lint-js", category: "LINT", riskLevel: "low", reason: "Lint/format validation command.", pattern: /^(npm|pnpm|yarn|bun)\s+(run\s+)?lint\b/, }, { id: "lint-generic", category: "LINT", riskLevel: "low", reason: "Static analysis or formatting command.", pattern: /^(eslint|ruff|golangci-lint|cargo\s+clippy|phpcs)\b/, }, { id: "test-run-script", category: "TEST", riskLevel: "low", reason: "Script execution for testing or verification.", pattern: /^(node|python|python3|deno\s+run|bun\s+run|npx\s+tsc|npx\s+ts-node|tsx)\b/, }, { id: "test-typecheck", category: "LINT", riskLevel: "low", reason: "Type checking command.", pattern: /^(tsc|npx\s+tsc)\b/, }, { id: "install-js", category: "INSTALL", riskLevel: "high", reason: "Dependency install or add command.", pattern: /^(npm|pnpm|yarn|bun)\s+(install|add)\b/, }, { id: "install-lang", category: "INSTALL", riskLevel: "high", reason: "Language package install command.", pattern: /^(go\s+install|pip\s+install|python\s+-m\s+pip\s+install|composer\s+install)\b/, }, { id: "destructive-shell", category: "DESTRUCTIVE", riskLevel: "critical", reason: "Destructive file removal command.", pattern: /^(rm\s+-rf|remove-item\b.*-recurse|del\s+\/s)\b/, }, { id: "destructive-git", category: "DESTRUCTIVE", riskLevel: "critical", reason: "Destructive git state reset command.", pattern: /^git\s+reset\s+--hard\b/, }, { id: "deploy-git", category: "DEPLOY", riskLevel: "high", reason: "Remote change publication command.", pattern: /^(git\s+push|gh\s+workflow\s+run|gh\s+release\s+create)\b/, }, { id: "deploy-platform", category: "DEPLOY", riskLevel: "high", reason: "Platform deployment command.", pattern: /^(vercel\s+deploy|netlify\s+deploy|docker\s+push)\b/, }, { id: "database-sql", category: "DATABASE", riskLevel: "high", reason: "Database mutation SQL command.", pattern: /^psql\b.*\b(drop|truncate|alter|create)\b/, }, { id: "database-migration-cli", category: "DATABASE", riskLevel: "high", reason: "Database migration workflow command.", pattern: /^(prisma\s+migrate|alembic\s+upgrade|sequelize\s+db:migrate|typeorm\s+migration:run)\b/, }, { id: "network-fetch", category: "NETWORK", riskLevel: "medium", reason: "Network fetch or remote script command.", pattern: /^(curl|wget|iwr|irm)\b/, }, ]; function normalize(command: string): string { return command.trim().replace(/\s+/g, " ").toLowerCase(); } // Strip cd/pushd/Set-Location prefix from compound commands so the actual command gets classified function stripDirectoryPrefix(command: string): string { return command .replace(/^(cd\s+\/d\s+\S+|cd\s+\S+|pushd\s+\S+|set-location\s+\S+)\s*(&&|;|\|)\s*/i, "") .replace(/^(cd\s+\/d\s+"[^"]+"|cd\s+"[^"]+"|pushd\s+"[^"]+")\s*(&&|;|\|)\s*/i, ""); } export function classifyCommand(command: string): CommandClassification { const stripped = stripDirectoryPrefix(command); const normalized = normalize(stripped); for (const rule of RULES) { if (rule.pattern.test(normalized)) { return { category: rule.category, riskLevel: rule.riskLevel, reason: rule.reason, matchedRule: rule.id, }; } } return { category: "UNKNOWN", riskLevel: "medium", reason: "No generic command rule matched.", matchedRule: "unknown-fallback", }; }