magicDocs.ts
services/MagicDocs/magicDocs.ts
255
Lines
7683
Bytes
4
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 integrations. It contains 255 lines, 11 detected imports, and 4 detected exports.
Important relationships
Detected exports
clearTrackedMagicDocsdetectMagicDocHeaderregisterMagicDocinitMagicDocs
Keywords
magiccontentmatchdocsfiletitlepathdocinfofilepathtrackedmagicdocs
Detected imports
../../Tool.js../../tools/AgentTool/loadAgentsDir.js../../tools/AgentTool/runAgent.js../../tools/FileEditTool/constants.js../../tools/FileReadTool/FileReadTool.js../../utils/errors.js../../utils/fileStateCache.js../../utils/hooks/postSamplingHooks.js../../utils/messages.js../../utils/sequential.js./prompts.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
/**
* 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)
}
}