source dump of claude code
at main 189 lines 6.4 kB view raw
1import { feature } from 'bun:bundle' 2import memoize from 'lodash-es/memoize.js' 3import { 4 getAdditionalDirectoriesForClaudeMd, 5 setCachedClaudeMdContent, 6} from './bootstrap/state.js' 7import { getLocalISODate } from './constants/common.js' 8import { 9 filterInjectedMemoryFiles, 10 getClaudeMds, 11 getMemoryFiles, 12} from './utils/claudemd.js' 13import { logForDiagnosticsNoPII } from './utils/diagLogs.js' 14import { isBareMode, isEnvTruthy } from './utils/envUtils.js' 15import { execFileNoThrow } from './utils/execFileNoThrow.js' 16import { getBranch, getDefaultBranch, getIsGit, gitExe } from './utils/git.js' 17import { shouldIncludeGitInstructions } from './utils/gitSettings.js' 18import { logError } from './utils/log.js' 19 20const MAX_STATUS_CHARS = 2000 21 22// System prompt injection for cache breaking (ant-only, ephemeral debugging state) 23let systemPromptInjection: string | null = null 24 25export function getSystemPromptInjection(): string | null { 26 return systemPromptInjection 27} 28 29export function setSystemPromptInjection(value: string | null): void { 30 systemPromptInjection = value 31 // Clear context caches immediately when injection changes 32 getUserContext.cache.clear?.() 33 getSystemContext.cache.clear?.() 34} 35 36export const getGitStatus = memoize(async (): Promise<string | null> => { 37 if (process.env.NODE_ENV === 'test') { 38 // Avoid cycles in tests 39 return null 40 } 41 42 const startTime = Date.now() 43 logForDiagnosticsNoPII('info', 'git_status_started') 44 45 const isGitStart = Date.now() 46 const isGit = await getIsGit() 47 logForDiagnosticsNoPII('info', 'git_is_git_check_completed', { 48 duration_ms: Date.now() - isGitStart, 49 is_git: isGit, 50 }) 51 52 if (!isGit) { 53 logForDiagnosticsNoPII('info', 'git_status_skipped_not_git', { 54 duration_ms: Date.now() - startTime, 55 }) 56 return null 57 } 58 59 try { 60 const gitCmdsStart = Date.now() 61 const [branch, mainBranch, status, log, userName] = await Promise.all([ 62 getBranch(), 63 getDefaultBranch(), 64 execFileNoThrow(gitExe(), ['--no-optional-locks', 'status', '--short'], { 65 preserveOutputOnError: false, 66 }).then(({ stdout }) => stdout.trim()), 67 execFileNoThrow( 68 gitExe(), 69 ['--no-optional-locks', 'log', '--oneline', '-n', '5'], 70 { 71 preserveOutputOnError: false, 72 }, 73 ).then(({ stdout }) => stdout.trim()), 74 execFileNoThrow(gitExe(), ['config', 'user.name'], { 75 preserveOutputOnError: false, 76 }).then(({ stdout }) => stdout.trim()), 77 ]) 78 79 logForDiagnosticsNoPII('info', 'git_commands_completed', { 80 duration_ms: Date.now() - gitCmdsStart, 81 status_length: status.length, 82 }) 83 84 // Check if status exceeds character limit 85 const truncatedStatus = 86 status.length > MAX_STATUS_CHARS 87 ? status.substring(0, MAX_STATUS_CHARS) + 88 '\n... (truncated because it exceeds 2k characters. If you need more information, run "git status" using BashTool)' 89 : status 90 91 logForDiagnosticsNoPII('info', 'git_status_completed', { 92 duration_ms: Date.now() - startTime, 93 truncated: status.length > MAX_STATUS_CHARS, 94 }) 95 96 return [ 97 `This is the git status at the start of the conversation. Note that this status is a snapshot in time, and will not update during the conversation.`, 98 `Current branch: ${branch}`, 99 `Main branch (you will usually use this for PRs): ${mainBranch}`, 100 ...(userName ? [`Git user: ${userName}`] : []), 101 `Status:\n${truncatedStatus || '(clean)'}`, 102 `Recent commits:\n${log}`, 103 ].join('\n\n') 104 } catch (error) { 105 logForDiagnosticsNoPII('error', 'git_status_failed', { 106 duration_ms: Date.now() - startTime, 107 }) 108 logError(error) 109 return null 110 } 111}) 112 113/** 114 * This context is prepended to each conversation, and cached for the duration of the conversation. 115 */ 116export const getSystemContext = memoize( 117 async (): Promise<{ 118 [k: string]: string 119 }> => { 120 const startTime = Date.now() 121 logForDiagnosticsNoPII('info', 'system_context_started') 122 123 // Skip git status in CCR (unnecessary overhead on resume) or when git instructions are disabled 124 const gitStatus = 125 isEnvTruthy(process.env.CLAUDE_CODE_REMOTE) || 126 !shouldIncludeGitInstructions() 127 ? null 128 : await getGitStatus() 129 130 // Include system prompt injection if set (for cache breaking, ant-only) 131 const injection = feature('BREAK_CACHE_COMMAND') 132 ? getSystemPromptInjection() 133 : null 134 135 logForDiagnosticsNoPII('info', 'system_context_completed', { 136 duration_ms: Date.now() - startTime, 137 has_git_status: gitStatus !== null, 138 has_injection: injection !== null, 139 }) 140 141 return { 142 ...(gitStatus && { gitStatus }), 143 ...(feature('BREAK_CACHE_COMMAND') && injection 144 ? { 145 cacheBreaker: `[CACHE_BREAKER: ${injection}]`, 146 } 147 : {}), 148 } 149 }, 150) 151 152/** 153 * This context is prepended to each conversation, and cached for the duration of the conversation. 154 */ 155export const getUserContext = memoize( 156 async (): Promise<{ 157 [k: string]: string 158 }> => { 159 const startTime = Date.now() 160 logForDiagnosticsNoPII('info', 'user_context_started') 161 162 // CLAUDE_CODE_DISABLE_CLAUDE_MDS: hard off, always. 163 // --bare: skip auto-discovery (cwd walk), BUT honor explicit --add-dir. 164 // --bare means "skip what I didn't ask for", not "ignore what I asked for". 165 const shouldDisableClaudeMd = 166 isEnvTruthy(process.env.CLAUDE_CODE_DISABLE_CLAUDE_MDS) || 167 (isBareMode() && getAdditionalDirectoriesForClaudeMd().length === 0) 168 // Await the async I/O (readFile/readdir directory walk) so the event 169 // loop yields naturally at the first fs.readFile. 170 const claudeMd = shouldDisableClaudeMd 171 ? null 172 : getClaudeMds(filterInjectedMemoryFiles(await getMemoryFiles())) 173 // Cache for the auto-mode classifier (yoloClassifier.ts reads this 174 // instead of importing claudemd.ts directly, which would create a 175 // cycle through permissions/filesystem → permissions → yoloClassifier). 176 setCachedClaudeMdContent(claudeMd || null) 177 178 logForDiagnosticsNoPII('info', 'user_context_completed', { 179 duration_ms: Date.now() - startTime, 180 claudemd_length: claudeMd?.length ?? 0, 181 claudemd_disabled: Boolean(shouldDisableClaudeMd), 182 }) 183 184 return { 185 ...(claudeMd && { claudeMd }), 186 currentDate: `Today's date is ${getLocalISODate()}.`, 187 } 188 }, 189)