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