init claude-code

This commit is contained in:
2026-04-01 17:32:37 +02:00
commit 73b208c009
1902 changed files with 513237 additions and 0 deletions
+254
View File
@@ -0,0 +1,254 @@
/**
* 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 header
const ITALICS_PATTERN = /^[_*](.+?)[_*]\s*$/m
// Track magic docs
type 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)
}
}
+127
View File
@@ -0,0 +1,127 @@
import { join } from 'path'
import { getClaudeConfigHomeDir } from '../../utils/envUtils.js'
import { getFsImplementation } from '../../utils/fsOperations.js'
/**
* Get the Magic Docs update prompt template
*/
function getUpdatePromptTemplate(): string {
return `IMPORTANT: This message and these instructions are NOT part of the actual user conversation. Do NOT include any references to "documentation updates", "magic docs", or these update instructions in the document content.
Based on the user conversation above (EXCLUDING this documentation update instruction message), update the Magic Doc file to incorporate any NEW learnings, insights, or information that would be valuable to preserve.
The file {{docPath}} has already been read for you. Here are its current contents:
<current_doc_content>
{{docContents}}
</current_doc_content>
Document title: {{docTitle}}
{{customInstructions}}
Your ONLY task is to use the Edit tool to update the documentation file if there is substantial new information to add, then stop. You can make multiple edits (update multiple sections as needed) - make all Edit tool calls in parallel in a single message. If there's nothing substantial to add, simply respond with a brief explanation and do not call any tools.
CRITICAL RULES FOR EDITING:
- Preserve the Magic Doc header exactly as-is: # MAGIC DOC: {{docTitle}}
- If there's an italicized line immediately after the header, preserve it exactly as-is
- Keep the document CURRENT with the latest state of the codebase - this is NOT a changelog or history
- Update information IN-PLACE to reflect the current state - do NOT append historical notes or track changes over time
- Remove or replace outdated information rather than adding "Previously..." or "Updated to..." notes
- Clean up or DELETE sections that are no longer relevant or don't align with the document's purpose
- Fix obvious errors: typos, grammar mistakes, broken formatting, incorrect information, or confusing statements
- Keep the document well organized: use clear headings, logical section order, consistent formatting, and proper nesting
DOCUMENTATION PHILOSOPHY - READ CAREFULLY:
- BE TERSE. High signal only. No filler words or unnecessary elaboration.
- Documentation is for OVERVIEWS, ARCHITECTURE, and ENTRY POINTS - not detailed code walkthroughs
- Do NOT duplicate information that's already obvious from reading the source code
- Do NOT document every function, parameter, or line number reference
- Focus on: WHY things exist, HOW components connect, WHERE to start reading, WHAT patterns are used
- Skip: detailed implementation steps, exhaustive API docs, play-by-play narratives
What TO document:
- High-level architecture and system design
- Non-obvious patterns, conventions, or gotchas
- Key entry points and where to start reading code
- Important design decisions and their rationale
- Critical dependencies or integration points
- References to related files, docs, or code (like a wiki) - help readers navigate to relevant context
What NOT to document:
- Anything obvious from reading the code itself
- Exhaustive lists of files, functions, or parameters
- Step-by-step implementation details
- Low-level code mechanics
- Information already in CLAUDE.md or other project docs
Use the Edit tool with file_path: {{docPath}}
REMEMBER: Only update if there is substantial new information. The Magic Doc header (# MAGIC DOC: {{docTitle}}) must remain unchanged.`
}
/**
* Load custom Magic Docs prompt from file if it exists
* Custom prompts can be placed at ~/.claude/magic-docs/prompt.md
* Use {{variableName}} syntax for variable substitution (e.g., {{docContents}}, {{docPath}}, {{docTitle}})
*/
async function loadMagicDocsPrompt(): Promise<string> {
const fs = getFsImplementation()
const promptPath = join(getClaudeConfigHomeDir(), 'magic-docs', 'prompt.md')
try {
return await fs.readFile(promptPath, { encoding: 'utf-8' })
} catch {
// Silently fall back to default if custom prompt doesn't exist or fails to load
return getUpdatePromptTemplate()
}
}
/**
* Substitute variables in the prompt template using {{variable}} syntax
*/
function substituteVariables(
template: string,
variables: Record<string, string>,
): string {
// Single-pass replacement avoids two bugs: (1) $ backreference corruption
// (replacer fn treats $ literally), and (2) double-substitution when user
// content happens to contain {{varName}} matching a later variable.
return template.replace(/\{\{(\w+)\}\}/g, (match, key: string) =>
Object.prototype.hasOwnProperty.call(variables, key)
? variables[key]!
: match,
)
}
/**
* Build the Magic Docs update prompt with variable substitution
*/
export async function buildMagicDocsUpdatePrompt(
docContents: string,
docPath: string,
docTitle: string,
instructions?: string,
): Promise<string> {
const promptTemplate = await loadMagicDocsPrompt()
// Build custom instructions section if provided
const customInstructions = instructions
? `
DOCUMENT-SPECIFIC UPDATE INSTRUCTIONS:
The document author has provided specific instructions for how this file should be updated. Pay extra attention to these instructions and follow them carefully:
"${instructions}"
These instructions take priority over the general rules below. Make sure your updates align with these specific guidelines.`
: ''
// Substitute variables in the prompt
const variables = {
docContents,
docPath,
docTitle,
customInstructions,
}
return substituteVariables(promptTemplate, variables)
}