/** * The authoring documentation, assembled from `skill/*.md` at module load. * * Two things are served from here: * - the always-on MCP server `instructions` (one file per backend mode) — * deliberately small, since every client pays for them on every turn; * - the deep authoring guide, split into TOPICS and served on demand by the * `how_it_works` tool. Nothing here is inlined into a tool schema: a doc * embedded in an input-schema description is paid for on every request * whether or not it's ever relevant. * * The prose lives in markdown (published via package.json's `files`) so it * can be edited without touching code. Paths resolve identically from * `src/mcp` (dev) and `lib/mcp` (published) — both are two levels below the * package root. * * Assembly has three steps: * 1. `` splices a shared block out of `SKILL.common.md` * (delimited there by ``), so the two mode guides * don't drift on the ~60% of prose that is identical. * 2. `%CTX%` expands to the mode's consumer-param namespace — `context.` on * the builder, `context_` on old-devtools. That is the only textual * difference across most shared blocks, so tokenizing it keeps them * shared. * 3. `` splits the result into the units `how_it_works` * serves. Backend-independent topics come from their own files instead. */ import assert from 'node:assert' import { readFileSync } from 'node:fs' import { fileURLToPath } from 'node:url' function readSkill(file: string): string { const url = new URL(`../../skill/${file}`, import.meta.url) return readFileSync(fileURLToPath(url), 'utf8') // Strip any leading frontmatter defensively. .replace(/^---\n[\s\S]*?\n---\n/, '') .trim() } export const BUILDER_MODE_INSTRUCTIONS = readSkill('builder-mode.md') export const OLD_MODE_INSTRUCTIONS = readSkill('old-mode.md') /** Split ``-delimited blocks, keeping document order. */ function splitBlocks(text: string, marker: string): Map { const blocks = new Map() const re = new RegExp( `\\n([\\s\\S]*?)(?=\\n/g, (_whole, name: string) => { const section = COMMON_SECTIONS.get(name) assert(section, `${file}: no SKILL.common.md section named "${name}"`) return section }) .replaceAll('%CTX%', ctxPrefix) const topics = splitBlocks(raw, 'topic') assert(topics.size, `${file}: no sections found`) for(const [name, path] of Object.entries(SHARED_TOPIC_FILES)) { topics.set(name, readSkill(path)) } const index = [...topics.keys()].map((topic) => { const summary = TOPIC_SUMMARIES[topic] assert(summary, `${file}: no TOPIC_SUMMARIES entry for "${topic}"`) return { topic, summary } }) return { topics, index, full: [...topics.values()].join('\n\n') } } export const GUIDE_BUILDER = buildGuide('SKILL.builder.md', 'context.') export const GUIDE_OLD = buildGuide('SKILL.old.md', 'context_') export function guideFor(oldMode: boolean): Guide { return oldMode ? GUIDE_OLD : GUIDE_BUILDER }