This pass rewrites comment-only ANT-ONLY markers to neutral internal-only language across the source tree without changing runtime strings, flags, commands, or protocol identifiers. The goal is to lower obvious internal prose leakage while keeping the diff mechanically safe and easy to review. Constraint: Phase B is limited to comments/prose only; runtime strings and user-facing labels remain deferred Rejected: Broad search-and-replace across strings and command descriptions | too risky for a prose-only pass Confidence: high Scope-risk: narrow Reversibility: clean Directive: Remaining ANT-ONLY hits are mostly runtime/user-facing strings and should be handled separately from comment cleanup Tested: bun run build Tested: bun run smoke Tested: bun run verify:privacy Tested: bun run test:provider Tested: bun run test:provider-recommendation Not-tested: Full repo typecheck (upstream baseline remains noisy) Co-authored-by: anandh8x <test@example.com>
190 lines
6.3 KiB
TypeScript
190 lines
6.3 KiB
TypeScript
import { feature } from 'bun:bundle'
|
|
import memoize from 'lodash-es/memoize.js'
|
|
import {
|
|
getAdditionalDirectoriesForClaudeMd,
|
|
setCachedClaudeMdContent,
|
|
} from './bootstrap/state.js'
|
|
import { getLocalISODate } from './constants/common.js'
|
|
import {
|
|
filterInjectedMemoryFiles,
|
|
getClaudeMds,
|
|
getMemoryFiles,
|
|
} from './utils/claudemd.js'
|
|
import { logForDiagnosticsNoPII } from './utils/diagLogs.js'
|
|
import { isBareMode, isEnvTruthy } from './utils/envUtils.js'
|
|
import { execFileNoThrow } from './utils/execFileNoThrow.js'
|
|
import { getBranch, getDefaultBranch, getIsGit, gitExe } from './utils/git.js'
|
|
import { shouldIncludeGitInstructions } from './utils/gitSettings.js'
|
|
import { logError } from './utils/log.js'
|
|
|
|
const MAX_STATUS_CHARS = 2000
|
|
|
|
// System prompt injection for cache breaking (internal-only, ephemeral debugging state)
|
|
let systemPromptInjection: string | null = null
|
|
|
|
export function getSystemPromptInjection(): string | null {
|
|
return systemPromptInjection
|
|
}
|
|
|
|
export function setSystemPromptInjection(value: string | null): void {
|
|
systemPromptInjection = value
|
|
// Clear context caches immediately when injection changes
|
|
getUserContext.cache.clear?.()
|
|
getSystemContext.cache.clear?.()
|
|
}
|
|
|
|
export const getGitStatus = memoize(async (): Promise<string | null> => {
|
|
if (process.env.NODE_ENV === 'test') {
|
|
// Avoid cycles in tests
|
|
return null
|
|
}
|
|
|
|
const startTime = Date.now()
|
|
logForDiagnosticsNoPII('info', 'git_status_started')
|
|
|
|
const isGitStart = Date.now()
|
|
const isGit = await getIsGit()
|
|
logForDiagnosticsNoPII('info', 'git_is_git_check_completed', {
|
|
duration_ms: Date.now() - isGitStart,
|
|
is_git: isGit,
|
|
})
|
|
|
|
if (!isGit) {
|
|
logForDiagnosticsNoPII('info', 'git_status_skipped_not_git', {
|
|
duration_ms: Date.now() - startTime,
|
|
})
|
|
return null
|
|
}
|
|
|
|
try {
|
|
const gitCmdsStart = Date.now()
|
|
const [branch, mainBranch, status, log, userName] = await Promise.all([
|
|
getBranch(),
|
|
getDefaultBranch(),
|
|
execFileNoThrow(gitExe(), ['--no-optional-locks', 'status', '--short'], {
|
|
preserveOutputOnError: false,
|
|
}).then(({ stdout }) => stdout.trim()),
|
|
execFileNoThrow(
|
|
gitExe(),
|
|
['--no-optional-locks', 'log', '--oneline', '-n', '5'],
|
|
{
|
|
preserveOutputOnError: false,
|
|
},
|
|
).then(({ stdout }) => stdout.trim()),
|
|
execFileNoThrow(gitExe(), ['config', 'user.name'], {
|
|
preserveOutputOnError: false,
|
|
}).then(({ stdout }) => stdout.trim()),
|
|
])
|
|
|
|
logForDiagnosticsNoPII('info', 'git_commands_completed', {
|
|
duration_ms: Date.now() - gitCmdsStart,
|
|
status_length: status.length,
|
|
})
|
|
|
|
// Check if status exceeds character limit
|
|
const truncatedStatus =
|
|
status.length > MAX_STATUS_CHARS
|
|
? status.substring(0, MAX_STATUS_CHARS) +
|
|
'\n... (truncated because it exceeds 2k characters. If you need more information, run "git status" using BashTool)'
|
|
: status
|
|
|
|
logForDiagnosticsNoPII('info', 'git_status_completed', {
|
|
duration_ms: Date.now() - startTime,
|
|
truncated: status.length > MAX_STATUS_CHARS,
|
|
})
|
|
|
|
return [
|
|
`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.`,
|
|
`Current branch: ${branch}`,
|
|
`Main branch (you will usually use this for PRs): ${mainBranch}`,
|
|
...(userName ? [`Git user: ${userName}`] : []),
|
|
`Status:\n${truncatedStatus || '(clean)'}`,
|
|
`Recent commits:\n${log}`,
|
|
].join('\n\n')
|
|
} catch (error) {
|
|
logForDiagnosticsNoPII('error', 'git_status_failed', {
|
|
duration_ms: Date.now() - startTime,
|
|
})
|
|
logError(error)
|
|
return null
|
|
}
|
|
})
|
|
|
|
/**
|
|
* This context is prepended to each conversation, and cached for the duration of the conversation.
|
|
*/
|
|
export const getSystemContext = memoize(
|
|
async (): Promise<{
|
|
[k: string]: string
|
|
}> => {
|
|
const startTime = Date.now()
|
|
logForDiagnosticsNoPII('info', 'system_context_started')
|
|
|
|
// Skip git status in CCR (unnecessary overhead on resume) or when git instructions are disabled
|
|
const gitStatus =
|
|
isEnvTruthy(process.env.CLAUDE_CODE_REMOTE) ||
|
|
!shouldIncludeGitInstructions()
|
|
? null
|
|
: await getGitStatus()
|
|
|
|
// Include system prompt injection if set (for cache breaking, internal-only)
|
|
const injection = feature('BREAK_CACHE_COMMAND')
|
|
? getSystemPromptInjection()
|
|
: null
|
|
|
|
logForDiagnosticsNoPII('info', 'system_context_completed', {
|
|
duration_ms: Date.now() - startTime,
|
|
has_git_status: gitStatus !== null,
|
|
has_injection: injection !== null,
|
|
})
|
|
|
|
return {
|
|
...(gitStatus && { gitStatus }),
|
|
...(feature('BREAK_CACHE_COMMAND') && injection
|
|
? {
|
|
cacheBreaker: `[CACHE_BREAKER: ${injection}]`,
|
|
}
|
|
: {}),
|
|
}
|
|
},
|
|
)
|
|
|
|
/**
|
|
* This context is prepended to each conversation, and cached for the duration of the conversation.
|
|
*/
|
|
export const getUserContext = memoize(
|
|
async (): Promise<{
|
|
[k: string]: string
|
|
}> => {
|
|
const startTime = Date.now()
|
|
logForDiagnosticsNoPII('info', 'user_context_started')
|
|
|
|
// CLAUDE_CODE_DISABLE_CLAUDE_MDS: hard off, always.
|
|
// --bare: skip auto-discovery (cwd walk), BUT honor explicit --add-dir.
|
|
// --bare means "skip what I didn't ask for", not "ignore what I asked for".
|
|
const shouldDisableClaudeMd =
|
|
isEnvTruthy(process.env.CLAUDE_CODE_DISABLE_CLAUDE_MDS) ||
|
|
(isBareMode() && getAdditionalDirectoriesForClaudeMd().length === 0)
|
|
// Await the async I/O (readFile/readdir directory walk) so the event
|
|
// loop yields naturally at the first fs.readFile.
|
|
const claudeMd = shouldDisableClaudeMd
|
|
? null
|
|
: getClaudeMds(filterInjectedMemoryFiles(await getMemoryFiles()))
|
|
// Cache for the auto-mode classifier (yoloClassifier.ts reads this
|
|
// instead of importing claudemd.ts directly, which would create a
|
|
// cycle through permissions/filesystem → permissions → yoloClassifier).
|
|
setCachedClaudeMdContent(claudeMd || null)
|
|
|
|
logForDiagnosticsNoPII('info', 'user_context_completed', {
|
|
duration_ms: Date.now() - startTime,
|
|
claudemd_length: claudeMd?.length ?? 0,
|
|
claudemd_disabled: Boolean(shouldDisableClaudeMd),
|
|
})
|
|
|
|
return {
|
|
...(claudeMd && { claudeMd }),
|
|
currentDate: `Today's date is ${getLocalISODate()}.`,
|
|
}
|
|
},
|
|
)
|