doctorContextWarnings.ts
utils/doctorContextWarnings.ts
266
Lines
8071
Bytes
3
Exports
11
Imports
10
Keywords
What this is
This page documents one file from the repository and includes its full source so you can read it without leaving the docs site.
Beginner explanation
This file is one piece of the larger system. Its name, directory, imports, and exports show where it fits. Start by reading the exports and related files first.
How it is used
Start from the exports list and related files. Those are the easiest clues for where this file fits into the system.
Expert explanation
Architecturally, this file intersects with repo-context. It contains 266 lines, 11 detected imports, and 3 detected exports.
Important relationships
Detected exports
ContextWarningContextWarningscheckContextWarnings
Keywords
lengthtokenstooltoolsdetailstolocalestringagentcontextwarningpromisecount
Detected imports
../services/tokenEstimation.js../Tool.js../tools/AgentTool/loadAgentsDir.js./analyzeContext.js./claudemd.js./model/model.js./permissions/permissionRuleParser.js./permissions/shadowedRuleDetection.js./sandbox/sandbox-adapter.js./statusNoticeHelpers.js./stringUtils.js
Source notes
This page embeds the full file contents. Small or leaf files are still indexed honestly instead of being over-explained.
Full source
import { roughTokenCountEstimation } from '../services/tokenEstimation.js'
import type { Tool, ToolPermissionContext } from '../Tool.js'
import type { AgentDefinitionsResult } from '../tools/AgentTool/loadAgentsDir.js'
import { countMcpToolTokens } from './analyzeContext.js'
import {
getLargeMemoryFiles,
getMemoryFiles,
MAX_MEMORY_CHARACTER_COUNT,
} from './claudemd.js'
import { getMainLoopModel } from './model/model.js'
import { permissionRuleValueToString } from './permissions/permissionRuleParser.js'
import { detectUnreachableRules } from './permissions/shadowedRuleDetection.js'
import { SandboxManager } from './sandbox/sandbox-adapter.js'
import {
AGENT_DESCRIPTIONS_THRESHOLD,
getAgentDescriptionsTotalTokens,
} from './statusNoticeHelpers.js'
import { plural } from './stringUtils.js'
// Thresholds (matching status notices and existing patterns)
const MCP_TOOLS_THRESHOLD = 25_000 // 15k tokens
export type ContextWarning = {
type:
| 'claudemd_files'
| 'agent_descriptions'
| 'mcp_tools'
| 'unreachable_rules'
severity: 'warning' | 'error'
message: string
details: string[]
currentValue: number
threshold: number
}
export type ContextWarnings = {
claudeMdWarning: ContextWarning | null
agentWarning: ContextWarning | null
mcpWarning: ContextWarning | null
unreachableRulesWarning: ContextWarning | null
}
async function checkClaudeMdFiles(): Promise<ContextWarning | null> {
const largeFiles = getLargeMemoryFiles(await getMemoryFiles())
// This already filters for files > 40k chars each
if (largeFiles.length === 0) {
return null
}
const details = largeFiles
.sort((a, b) => b.content.length - a.content.length)
.map(file => `${file.path}: ${file.content.length.toLocaleString()} chars`)
const message =
largeFiles.length === 1
? `Large CLAUDE.md file detected (${largeFiles[0]!.content.length.toLocaleString()} chars > ${MAX_MEMORY_CHARACTER_COUNT.toLocaleString()})`
: `${largeFiles.length} large CLAUDE.md files detected (each > ${MAX_MEMORY_CHARACTER_COUNT.toLocaleString()} chars)`
return {
type: 'claudemd_files',
severity: 'warning',
message,
details,
currentValue: largeFiles.length, // Number of files exceeding threshold
threshold: MAX_MEMORY_CHARACTER_COUNT,
}
}
/**
* Check agent descriptions token count
*/
async function checkAgentDescriptions(
agentInfo: AgentDefinitionsResult | null,
): Promise<ContextWarning | null> {
if (!agentInfo) {
return null
}
const totalTokens = getAgentDescriptionsTotalTokens(agentInfo)
if (totalTokens <= AGENT_DESCRIPTIONS_THRESHOLD) {
return null
}
// Calculate tokens for each agent
const agentTokens = agentInfo.activeAgents
.filter(a => a.source !== 'built-in')
.map(agent => {
const description = `${agent.agentType}: ${agent.whenToUse}`
return {
name: agent.agentType,
tokens: roughTokenCountEstimation(description),
}
})
.sort((a, b) => b.tokens - a.tokens)
const details = agentTokens
.slice(0, 5)
.map(agent => `${agent.name}: ~${agent.tokens.toLocaleString()} tokens`)
if (agentTokens.length > 5) {
details.push(`(${agentTokens.length - 5} more custom agents)`)
}
return {
type: 'agent_descriptions',
severity: 'warning',
message: `Large agent descriptions (~${totalTokens.toLocaleString()} tokens > ${AGENT_DESCRIPTIONS_THRESHOLD.toLocaleString()})`,
details,
currentValue: totalTokens,
threshold: AGENT_DESCRIPTIONS_THRESHOLD,
}
}
/**
* Check MCP tools token count
*/
async function checkMcpTools(
tools: Tool[],
getToolPermissionContext: () => Promise<ToolPermissionContext>,
agentInfo: AgentDefinitionsResult | null,
): Promise<ContextWarning | null> {
const mcpTools = tools.filter(tool => tool.isMcp)
// Note: MCP tools are loaded asynchronously and may not be available
// when doctor command runs, as it executes before MCP connections are established
if (mcpTools.length === 0) {
return null
}
try {
// Use the existing countMcpToolTokens function from analyzeContext
const model = getMainLoopModel()
const { mcpToolTokens, mcpToolDetails } = await countMcpToolTokens(
tools,
getToolPermissionContext,
agentInfo,
model,
)
if (mcpToolTokens <= MCP_TOOLS_THRESHOLD) {
return null
}
// Group tools by server
const toolsByServer = new Map<string, { count: number; tokens: number }>()
for (const tool of mcpToolDetails) {
// Extract server name from tool name (format: mcp__servername__toolname)
const parts = tool.name.split('__')
const serverName = parts[1] || 'unknown'
const current = toolsByServer.get(serverName) || { count: 0, tokens: 0 }
toolsByServer.set(serverName, {
count: current.count + 1,
tokens: current.tokens + tool.tokens,
})
}
// Sort servers by token count
const sortedServers = Array.from(toolsByServer.entries()).sort(
(a, b) => b[1].tokens - a[1].tokens,
)
const details = sortedServers
.slice(0, 5)
.map(
([name, info]) =>
`${name}: ${info.count} tools (~${info.tokens.toLocaleString()} tokens)`,
)
if (sortedServers.length > 5) {
details.push(`(${sortedServers.length - 5} more servers)`)
}
return {
type: 'mcp_tools',
severity: 'warning',
message: `Large MCP tools context (~${mcpToolTokens.toLocaleString()} tokens > ${MCP_TOOLS_THRESHOLD.toLocaleString()})`,
details,
currentValue: mcpToolTokens,
threshold: MCP_TOOLS_THRESHOLD,
}
} catch (_error) {
// If token counting fails, fall back to character-based estimation
const estimatedTokens = mcpTools.reduce((total, tool) => {
const chars = (tool.name?.length || 0) + tool.description.length
return total + roughTokenCountEstimation(chars.toString())
}, 0)
if (estimatedTokens <= MCP_TOOLS_THRESHOLD) {
return null
}
return {
type: 'mcp_tools',
severity: 'warning',
message: `Large MCP tools context (~${estimatedTokens.toLocaleString()} tokens estimated > ${MCP_TOOLS_THRESHOLD.toLocaleString()})`,
details: [
`${mcpTools.length} MCP tools detected (token count estimated)`,
],
currentValue: estimatedTokens,
threshold: MCP_TOOLS_THRESHOLD,
}
}
}
/**
* Check for unreachable permission rules (e.g., specific allow rules shadowed by tool-wide ask rules)
*/
async function checkUnreachableRules(
getToolPermissionContext: () => Promise<ToolPermissionContext>,
): Promise<ContextWarning | null> {
const context = await getToolPermissionContext()
const sandboxAutoAllowEnabled =
SandboxManager.isSandboxingEnabled() &&
SandboxManager.isAutoAllowBashIfSandboxedEnabled()
const unreachable = detectUnreachableRules(context, {
sandboxAutoAllowEnabled,
})
if (unreachable.length === 0) {
return null
}
const details = unreachable.flatMap(r => [
`${permissionRuleValueToString(r.rule.ruleValue)}: ${r.reason}`,
` Fix: ${r.fix}`,
])
return {
type: 'unreachable_rules',
severity: 'warning',
message: `${unreachable.length} ${plural(unreachable.length, 'unreachable permission rule')} detected`,
details,
currentValue: unreachable.length,
threshold: 0,
}
}
/**
* Check all context warnings for the doctor command
*/
export async function checkContextWarnings(
tools: Tool[],
agentInfo: AgentDefinitionsResult | null,
getToolPermissionContext: () => Promise<ToolPermissionContext>,
): Promise<ContextWarnings> {
const [claudeMdWarning, agentWarning, mcpWarning, unreachableRulesWarning] =
await Promise.all([
checkClaudeMdFiles(),
checkAgentDescriptions(agentInfo),
checkMcpTools(tools, getToolPermissionContext, agentInfo),
checkUnreachableRules(getToolPermissionContext),
])
return {
claudeMdWarning,
agentWarning,
mcpWarning,
unreachableRulesWarning,
}
}