172 lines
5.4 KiB
TypeScript
172 lines
5.4 KiB
TypeScript
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<AgentPromptSegmentKey, string>;
|
|
|
|
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 = `<!-- AGENT_PROMPT_SEGMENT:${key}:start -->`;
|
|
const endMarker = `<!-- AGENT_PROMPT_SEGMENT:${key}:end -->`;
|
|
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<AgentDefinitionId, AgentPromptSegments> {
|
|
const compiled = {} as Record<AgentDefinitionId, AgentPromptSegments>;
|
|
|
|
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<AgentDefinitionId, AgentPromptSegments>,
|
|
): 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<AgentDocPromptSegmentKey, string>;",
|
|
"",
|
|
"export const AGENT_DOC_SEGMENTS: Record<AgentDefinitionId, AgentDocPromptSegments> = {",
|
|
];
|
|
|
|
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<string> {
|
|
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;
|
|
});
|
|
}
|