Something went wrong. Try again.
source dump of claude code forked from oppi.li/claude-code
Something went wrong. Try again.
7.7 kB · 254 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255/** * Magic Docs automatically maintains markdown documentation files marked with special headers. * When a file with "# MAGIC DOC: [title]" is read, it runs periodically in the background * using a forked subagent to update the document with new learnings from the conversation. * * See docs/magic-docs.md for more information. */
import type { Tool, ToolUseContext } from '../../Tool.js'import type { BuiltInAgentDefinition } from '../../tools/AgentTool/loadAgentsDir.js'import { runAgent } from '../../tools/AgentTool/runAgent.js'import { FILE_EDIT_TOOL_NAME } from '../../tools/FileEditTool/constants.js'import { FileReadTool, type Output as FileReadToolOutput, registerFileReadListener,} from '../../tools/FileReadTool/FileReadTool.js'import { isFsInaccessible } from '../../utils/errors.js'import { cloneFileStateCache } from '../../utils/fileStateCache.js'import { type REPLHookContext, registerPostSamplingHook,} from '../../utils/hooks/postSamplingHooks.js'import { createUserMessage, hasToolCallsInLastAssistantTurn,} from '../../utils/messages.js'import { sequential } from '../../utils/sequential.js'import { buildMagicDocsUpdatePrompt } from './prompts.js'
// Magic Doc header pattern: # MAGIC DOC: [title]// Matches at the start of the file (first line)const MAGIC_DOC_HEADER_PATTERN = /^#\s*MAGIC\s+DOC:\s*(.+)$/im// Pattern to match italics on the line immediately after the headerconst ITALICS_PATTERN = /^[_*](.+?)[_*]\s*$/m
// Track magic docstype MagicDocInfo = { path: string}
const trackedMagicDocs = new Map<string, MagicDocInfo>()
export function clearTrackedMagicDocs(): void { trackedMagicDocs.clear()}
/** * Detect if a file content contains a Magic Doc header * Returns an object with title and optional instructions, or null if not a magic doc */export function detectMagicDocHeader( content: string,): { title: string; instructions?: string } | null { const match = content.match(MAGIC_DOC_HEADER_PATTERN) if (!match || !match[1]) { return null }
const title = match[1].trim()
// Look for italics on the next line after the header (allow one optional blank line) const headerEndIndex = match.index! + match[0].length const afterHeader = content.slice(headerEndIndex) // Match: newline, optional blank line, then content line const nextLineMatch = afterHeader.match(/^\s*\n(?:\s*\n)?(.+?)(?:\n|$)/)
if (nextLineMatch && nextLineMatch[1]) { const nextLine = nextLineMatch[1] const italicsMatch = nextLine.match(ITALICS_PATTERN) if (italicsMatch && italicsMatch[1]) { const instructions = italicsMatch[1].trim() return { title, instructions, } } }
return { title }}
/** * Register a file as a Magic Doc when it's read * Only registers once per file path - the hook always reads latest content */export function registerMagicDoc(filePath: string): void { // Only register if not already tracked if (!trackedMagicDocs.has(filePath)) { trackedMagicDocs.set(filePath, { path: filePath, }) }}
/** * Create Magic Docs agent definition */function getMagicDocsAgent(): BuiltInAgentDefinition { return { agentType: 'magic-docs', whenToUse: 'Update Magic Docs', tools: [FILE_EDIT_TOOL_NAME], // Only allow Edit model: 'sonnet', source: 'built-in', baseDir: 'built-in', getSystemPrompt: () => '', // Will use override systemPrompt }}
/** * Update a single Magic Doc */async function updateMagicDoc( docInfo: MagicDocInfo, context: REPLHookContext,): Promise<void> { const { messages, systemPrompt, userContext, systemContext, toolUseContext } = context
// Clone the FileStateCache to isolate Magic Docs operations. Delete this // doc's entry so FileReadTool's dedup doesn't return a file_unchanged // stub — we need the actual content to re-detect the header. const clonedReadFileState = cloneFileStateCache(toolUseContext.readFileState) clonedReadFileState.delete(docInfo.path) const clonedToolUseContext: ToolUseContext = { ...toolUseContext, readFileState: clonedReadFileState, }
// Read the document; if deleted or unreadable, remove from tracking let currentDoc = '' try { const result = await FileReadTool.call( { file_path: docInfo.path }, clonedToolUseContext, ) const output = result.data as FileReadToolOutput if (output.type === 'text') { currentDoc = output.file.content } } catch (e: unknown) { // FileReadTool wraps ENOENT in a plain Error("File does not exist...") with // no .code, so check the message in addition to isFsInaccessible (EACCES/EPERM). if ( isFsInaccessible(e) || (e instanceof Error && e.message.startsWith('File does not exist')) ) { trackedMagicDocs.delete(docInfo.path) return } throw e }
// Re-detect title and instructions from latest file content const detected = detectMagicDocHeader(currentDoc) if (!detected) { // File no longer has magic doc header, remove from tracking trackedMagicDocs.delete(docInfo.path) return }
// Build update prompt with latest title and instructions const userPrompt = await buildMagicDocsUpdatePrompt( currentDoc, docInfo.path, detected.title, detected.instructions, )
// Create a custom canUseTool that only allows Edit for magic doc files const canUseTool = async (tool: Tool, input: unknown) => { if ( tool.name === FILE_EDIT_TOOL_NAME && typeof input === 'object' && input !== null && 'file_path' in input ) { const filePath = input.file_path if (typeof filePath === 'string' && filePath === docInfo.path) { return { behavior: 'allow' as const, updatedInput: input } } } return { behavior: 'deny' as const, message: `only ${FILE_EDIT_TOOL_NAME} is allowed for ${docInfo.path}`, decisionReason: { type: 'other' as const, reason: `only ${FILE_EDIT_TOOL_NAME} is allowed`, }, } }
// Run Magic Docs update using runAgent with forked context for await (const _message of runAgent({ agentDefinition: getMagicDocsAgent(), promptMessages: [createUserMessage({ content: userPrompt })], toolUseContext: clonedToolUseContext, canUseTool, isAsync: true, forkContextMessages: messages, querySource: 'magic_docs', override: { systemPrompt, userContext, systemContext, }, availableTools: clonedToolUseContext.options.tools, })) { // Just consume - let it run to completion }}
/** * Magic Docs post-sampling hook that updates all tracked Magic Docs */const updateMagicDocs = sequential(async function ( context: REPLHookContext,): Promise<void> { const { messages, querySource } = context
if (querySource !== 'repl_main_thread') { return }
// Only update when conversation is idle (no tool calls in last turn) const hasToolCalls = hasToolCallsInLastAssistantTurn(messages) if (hasToolCalls) { return }
const docCount = trackedMagicDocs.size if (docCount === 0) { return }
for (const docInfo of Array.from(trackedMagicDocs.values())) { await updateMagicDoc(docInfo, context) }})
export async function initMagicDocs(): Promise<void> { if (process.env.USER_TYPE === 'ant') { // Register listener to detect magic docs when files are read registerFileReadListener((filePath: string, content: string) => { const result = detectMagicDocHeader(content) if (result) { registerMagicDoc(filePath) } })
registerPostSamplingHook(updateMagicDocs) }}