autoMode.ts
1 /** 2 * Auto mode subcommand handlers — dump default/merged classifier rules and 3 * critique user-written rules. Dynamically imported when `claude auto-mode ...` runs. 4 */ 5 6 import { errorMessage } from '../../utils/errors.js' 7 import { 8 getMainLoopModel, 9 parseUserSpecifiedModel, 10 } from '../../utils/model/model.js' 11 import { 12 type AutoModeRules, 13 buildDefaultExternalSystemPrompt, 14 getDefaultExternalAutoModeRules, 15 } from '../../utils/permissions/yoloClassifier.js' 16 import { getAutoModeConfig } from '../../utils/settings/settings.js' 17 import { sideQuery } from '../../utils/sideQuery.js' 18 import { jsonStringify } from '../../utils/slowOperations.js' 19 20 function writeRules(rules: AutoModeRules): void { 21 process.stdout.write(jsonStringify(rules, null, 2) + '\n') 22 } 23 24 export function autoModeDefaultsHandler(): void { 25 writeRules(getDefaultExternalAutoModeRules()) 26 } 27 28 /** 29 * Dump the effective auto mode config: user settings where provided, external 30 * defaults otherwise. Per-section REPLACE semantics — matches how 31 * buildYoloSystemPrompt resolves the external template (a non-empty user 32 * section replaces that section's defaults entirely; an empty/absent section 33 * falls through to defaults). 34 */ 35 export function autoModeConfigHandler(): void { 36 const config = getAutoModeConfig() 37 const defaults = getDefaultExternalAutoModeRules() 38 writeRules({ 39 allow: config?.allow?.length ? config.allow : defaults.allow, 40 soft_deny: config?.soft_deny?.length 41 ? config.soft_deny 42 : defaults.soft_deny, 43 environment: config?.environment?.length 44 ? config.environment 45 : defaults.environment, 46 }) 47 } 48 49 const CRITIQUE_SYSTEM_PROMPT = 50 'You are an expert reviewer of auto mode classifier rules for Claude Code.\n' + 51 '\n' + 52 'Claude Code has an "auto mode" that uses an AI classifier to decide whether ' + 53 'tool calls should be auto-approved or require user confirmation. Users can ' + 54 'write custom rules in three categories:\n' + 55 '\n' + 56 '- **allow**: Actions the classifier should auto-approve\n' + 57 '- **soft_deny**: Actions the classifier should block (require user confirmation)\n' + 58 "- **environment**: Context about the user's setup that helps the classifier make decisions\n" + 59 '\n' + 60 "Your job is to critique the user's custom rules for clarity, completeness, " + 61 'and potential issues. The classifier is an LLM that reads these rules as ' + 62 'part of its system prompt.\n' + 63 '\n' + 64 'For each rule, evaluate:\n' + 65 '1. **Clarity**: Is the rule unambiguous? Could the classifier misinterpret it?\n' + 66 "2. **Completeness**: Are there gaps or edge cases the rule doesn't cover?\n" + 67 '3. **Conflicts**: Do any of the rules conflict with each other?\n' + 68 '4. **Actionability**: Is the rule specific enough for the classifier to act on?\n' + 69 '\n' + 70 'Be concise and constructive. Only comment on rules that could be improved. ' + 71 'If all rules look good, say so.' 72 73 export async function autoModeCritiqueHandler(options: { 74 model?: string 75 }): Promise<void> { 76 const config = getAutoModeConfig() 77 const hasCustomRules = 78 (config?.allow?.length ?? 0) > 0 || 79 (config?.soft_deny?.length ?? 0) > 0 || 80 (config?.environment?.length ?? 0) > 0 81 82 if (!hasCustomRules) { 83 process.stdout.write( 84 'No custom auto mode rules found.\n\n' + 85 'Add rules to your settings file under autoMode.{allow, soft_deny, environment}.\n' + 86 'Run `claude auto-mode defaults` to see the default rules for reference.\n', 87 ) 88 return 89 } 90 91 const model = options.model 92 ? parseUserSpecifiedModel(options.model) 93 : getMainLoopModel() 94 95 const defaults = getDefaultExternalAutoModeRules() 96 const classifierPrompt = buildDefaultExternalSystemPrompt() 97 98 const userRulesSummary = 99 formatRulesForCritique('allow', config?.allow ?? [], defaults.allow) + 100 formatRulesForCritique( 101 'soft_deny', 102 config?.soft_deny ?? [], 103 defaults.soft_deny, 104 ) + 105 formatRulesForCritique( 106 'environment', 107 config?.environment ?? [], 108 defaults.environment, 109 ) 110 111 process.stdout.write('Analyzing your auto mode rules…\n\n') 112 113 let response 114 try { 115 response = await sideQuery({ 116 querySource: 'auto_mode_critique', 117 model, 118 system: CRITIQUE_SYSTEM_PROMPT, 119 skipSystemPromptPrefix: true, 120 max_tokens: 4096, 121 messages: [ 122 { 123 role: 'user', 124 content: 125 'Here is the full classifier system prompt that the auto mode classifier receives:\n\n' + 126 '<classifier_system_prompt>\n' + 127 classifierPrompt + 128 '\n</classifier_system_prompt>\n\n' + 129 "Here are the user's custom rules that REPLACE the corresponding default sections:\n\n" + 130 userRulesSummary + 131 '\nPlease critique these custom rules.', 132 }, 133 ], 134 }) 135 } catch (error) { 136 process.stderr.write( 137 'Failed to analyze rules: ' + errorMessage(error) + '\n', 138 ) 139 process.exitCode = 1 140 return 141 } 142 143 const textBlock = response.content.find(block => block.type === 'text') 144 if (textBlock?.type === 'text') { 145 process.stdout.write(textBlock.text + '\n') 146 } else { 147 process.stdout.write('No critique was generated. Please try again.\n') 148 } 149 } 150 151 function formatRulesForCritique( 152 section: string, 153 userRules: string[], 154 defaultRules: string[], 155 ): string { 156 if (userRules.length === 0) return '' 157 const customLines = userRules.map(r => '- ' + r).join('\n') 158 const defaultLines = defaultRules.map(r => '- ' + r).join('\n') 159 return ( 160 '## ' + 161 section + 162 ' (custom rules replacing defaults)\n' + 163 'Custom:\n' + 164 customLines + 165 '\n\n' + 166 'Defaults being replaced:\n' + 167 defaultLines + 168 '\n\n' 169 ) 170 }