import { promises as fs } from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { AGENT_DEFINITIONS, type AgentDefinitionId } from "../lib/agent-definitions"; export const AGENT_PROMPT_SEGMENT_KEYS = [ "role", "style-rules", "decision-framework", "channel-notes", ] as const; export type AgentPromptSegmentKey = (typeof AGENT_PROMPT_SEGMENT_KEYS)[number]; export type AgentPromptSegments = Record; type CompileOptions = { sourcePath: string; }; function normalizeSegmentBody(raw: string): string { return raw .replace(/\r\n/g, "\n") .split("\n") .map((line) => line.trimEnd()) .join("\n") .trim(); } function countMarkerOccurrences(markdown: string, marker: string): number { let count = 0; let currentIndex = 0; while (currentIndex >= 0) { currentIndex = markdown.indexOf(marker, currentIndex); if (currentIndex < 0) { break; } count += 1; currentIndex += marker.length; } return count; } function extractSegment(markdown: string, key: AgentPromptSegmentKey, sourcePath: string): string { const startMarker = ``; const endMarker = ``; const startCount = countMarkerOccurrences(markdown, startMarker); const endCount = countMarkerOccurrences(markdown, endMarker); if (startCount !== 1 || endCount !== 1) { throw new Error( `[agent-docs] Missing or duplicate markers for '${key}' in ${sourcePath}. Expected exactly one start and one end marker.`, ); } const startIndex = markdown.indexOf(startMarker); const contentStart = startIndex + startMarker.length; const endIndex = markdown.indexOf(endMarker, contentStart); if (startIndex < 0 || endIndex < 0 || endIndex <= contentStart) { throw new Error(`[agent-docs] Invalid marker placement for '${key}' in ${sourcePath}.`); } const segment = normalizeSegmentBody(markdown.slice(contentStart, endIndex)); if (segment === "") { throw new Error(`[agent-docs] Empty segment '${key}' in ${sourcePath}.`); } return segment; } export function compileAgentDocSegmentsFromMarkdown( markdown: string, options: CompileOptions, ): AgentPromptSegments { const compiled = {} as AgentPromptSegments; for (const key of AGENT_PROMPT_SEGMENT_KEYS) { compiled[key] = extractSegment(markdown, key, options.sourcePath); } return compiled; } export function compileAgentDocSegmentsFromSources( sources: ReadonlyArray<{ agentId: AgentDefinitionId; markdown: string; sourcePath: string }>, ): Record { const compiled = {} as Record; for (const source of sources) { compiled[source.agentId] = compileAgentDocSegmentsFromMarkdown(source.markdown, { sourcePath: source.sourcePath, }); } return compiled; } function escapeForTsString(raw: string): string { return raw.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${"); } export function renderCompiledAgentDocSegmentsModule( compiled: Record, ): string { const lines: string[] = [ "// This file is generated by scripts/compile-agent-docs.ts", "// Do not edit manually.", "", 'import type { AgentDefinitionId } from "@/lib/agent-definitions";', "", "export type AgentDocPromptSegmentKey =", ' | "role"', ' | "style-rules"', ' | "decision-framework"', ' | "channel-notes";', "", "export type AgentDocPromptSegments = Record;", "", "export const AGENT_DOC_SEGMENTS: Record = {", ]; const agentIds = Object.keys(compiled).sort() as AgentDefinitionId[]; for (const agentId of agentIds) { lines.push(` \"${agentId}\": {`); for (const key of AGENT_PROMPT_SEGMENT_KEYS) { lines.push(` \"${key}\": \`${escapeForTsString(compiled[agentId][key])}\`,`); } lines.push(" },"); } lines.push("};", ""); return lines.join("\n"); } export async function compileAgentDocsToGeneratedModule(projectRoot: string): Promise { const sources: Array<{ agentId: AgentDefinitionId; markdown: string; sourcePath: string }> = []; for (const definition of AGENT_DEFINITIONS) { const sourcePath = definition.docs.markdownPath; const absoluteSourcePath = path.resolve(projectRoot, sourcePath); const markdown = await fs.readFile(absoluteSourcePath, "utf8"); sources.push({ agentId: definition.id, markdown, sourcePath }); } const compiled = compileAgentDocSegmentsFromSources(sources); const output = renderCompiledAgentDocSegmentsModule(compiled); const outputPath = path.resolve(projectRoot, "lib/generated/agent-doc-segments.ts"); await fs.mkdir(path.dirname(outputPath), { recursive: true }); await fs.writeFile(outputPath, output, "utf8"); return outputPath; } const thisFilePath = fileURLToPath(import.meta.url); const invokedPath = process.argv[1] ? path.resolve(process.argv[1]) : ""; if (invokedPath === thisFilePath) { compileAgentDocsToGeneratedModule(process.cwd()) .then((outputPath) => { process.stdout.write(`[agent-docs] Wrote ${outputPath}\n`); }) .catch((error) => { const message = error instanceof Error ? error.message : String(error); process.stderr.write(`${message}\n`); process.exitCode = 1; }); }