/** * Context generation for AI agents. * * Generates AI-ready context documents from compiled fragment data. */ import type { CompiledFragment, CompiledBlock, PropDefinition } from '../compiled-types/index.js'; /** * Placeholder patterns to filter out from usage text. */ export const PLACEHOLDER_PATTERNS = [ /^\w+ component is needed$/i, /^Alternative component is more appropriate$/i, /^Use \w+ when you need/i, ]; /** * Filter out placeholder text from usage arrays */ export function filterPlaceholders(items: string[] | undefined): string[] { if (!items) return []; return items.filter(item => !PLACEHOLDER_PATTERNS.some(pattern => pattern.test(item.trim())) ); } /** * Options for context generation */ export interface ContextOptions { format?: "markdown" | "json"; include?: { props?: boolean; variants?: boolean; usage?: boolean; relations?: boolean; code?: boolean; }; compact?: boolean; } /** * Result of context generation */ export interface ContextResult { content: string; tokenEstimate: number; } /** * Generate AI-ready context from compiled fragments and optional blocks */ export function generateContext( fragments: CompiledFragment[], options: ContextOptions = {}, blocks?: CompiledBlock[] ): ContextResult { const format = options.format ?? "markdown"; const compact = options.compact ?? false; const include = { props: options.include?.props ?? true, variants: options.include?.variants ?? true, usage: options.include?.usage ?? true, relations: options.include?.relations ?? false, code: options.include?.code ?? false, }; const sorted = [...fragments].sort((a, b) => { const catCompare = a.meta.category.localeCompare(b.meta.category); if (catCompare !== 0) return catCompare; return a.meta.name.localeCompare(b.meta.name); }); if (format === "json") { return generateJsonContext(sorted, include, compact, blocks); } return generateMarkdownContext(sorted, include, compact, blocks); } function generateMarkdownContext( fragments: CompiledFragment[], include: Required>, compact: boolean, blocks?: CompiledBlock[] ): ContextResult { const lines: string[] = []; lines.push("# Design System Reference"); lines.push(""); lines.push("## Quick Reference"); lines.push(""); lines.push("| Component | Category | Use For |"); lines.push("|-----------|----------|---------|"); for (const fragment of fragments) { const filteredWhen = filterPlaceholders(fragment.usage.when); const useFor = filteredWhen.slice(0, 2).join(", ") || fragment.meta.description; lines.push(`| ${fragment.meta.name} | ${fragment.meta.category} | ${truncate(useFor, 50)} |`); } lines.push(""); if (compact) { const content = lines.join("\n"); return { content, tokenEstimate: estimateTokens(content) }; } lines.push("## Components"); lines.push(""); for (const fragment of fragments) { lines.push(`### ${fragment.meta.name}`); lines.push(""); const statusParts = [`**Category:** ${fragment.meta.category}`]; if (fragment.meta.status) { statusParts.push(`**Status:** ${fragment.meta.status}`); } lines.push(statusParts.join(" | ")); lines.push(""); if (fragment.meta.description) { lines.push(fragment.meta.description); lines.push(""); } const whenFiltered = filterPlaceholders(fragment.usage.when); const whenNotFiltered = filterPlaceholders(fragment.usage.whenNot); if (include.usage && (whenFiltered.length > 0 || whenNotFiltered.length > 0)) { if (whenFiltered.length > 0) { lines.push("**When to use:**"); for (const when of whenFiltered) { lines.push(`- ${when}`); } lines.push(""); } if (whenNotFiltered.length > 0) { lines.push("**When NOT to use:**"); for (const whenNot of whenNotFiltered) { lines.push(`- ${whenNot}`); } lines.push(""); } } // Composition data (from ai metadata) if (fragment.ai?.compositionPattern) { const ai = fragment.ai; const parts: string[] = [`**Composition:** ${ai.compositionPattern}`]; if (ai.subComponents && ai.subComponents.length > 0) { parts.push(`Sub-components: ${ai.subComponents.map(s => `${fragment.meta.name}.${s}`).join(', ')}`); } if (ai.requiredChildren && ai.requiredChildren.length > 0) { parts.push(`Required: ${ai.requiredChildren.map(c => `${fragment.meta.name}.${c}`).join(', ')}`); } lines.push(parts.join(' | ')); lines.push(""); if (ai.commonPatterns && ai.commonPatterns.length > 0) { lines.push("**Patterns:**"); for (const pattern of ai.commonPatterns) { lines.push(`- \`${pattern}\``); } lines.push(""); } } // Contract data if (fragment.contract) { const contract = fragment.contract; if (contract.propsSummary && contract.propsSummary.length > 0) { lines.push(`**Props:** ${contract.propsSummary.join(', ')}`); lines.push(""); } else if (include.props && Object.keys(fragment.props).length > 0) { lines.push("**Props:**"); for (const [name, prop] of Object.entries(fragment.props)) { lines.push(`- \`${name}\`: ${formatPropType(prop)}${prop.required ? " (required)" : ""}`); } lines.push(""); } if (contract.compoundChildren && Object.keys(contract.compoundChildren).length > 0) { lines.push("**Sub-components:**"); for (const [childName, childMeta] of Object.entries(contract.compoundChildren)) { const parts: string[] = [`\`${fragment.meta.name}.${childName}\``]; if (childMeta.required) parts.push("(required)"); if (childMeta.description) parts.push(`— ${childMeta.description}`); lines.push(`- ${parts.join(' ')}`); } lines.push(""); } if (contract.canonicalUsage && contract.canonicalUsage.length > 0) { lines.push("**Usage examples:**"); for (const usage of contract.canonicalUsage) { lines.push("```tsx"); lines.push(usage); lines.push("```"); } lines.push(""); } if (contract.a11yRules && contract.a11yRules.length > 0) { lines.push(`**A11y:** ${contract.a11yRules.join(', ')}`); lines.push(""); } if (contract.bans && contract.bans.length > 0) { lines.push("**Banned patterns:**"); for (const ban of contract.bans) { lines.push(`- \`${ban.pattern}\`: ${ban.message}`); } lines.push(""); } } else if (include.props && Object.keys(fragment.props).length > 0) { lines.push("**Props:**"); for (const [name, prop] of Object.entries(fragment.props)) { lines.push(`- \`${name}\`: ${formatPropType(prop)}${prop.required ? " (required)" : ""}`); } lines.push(""); } if (include.variants && fragment.variants.length > 0) { const variantNames = fragment.variants.map((v) => v.name).join(", "); lines.push(`**Variants:** ${variantNames}`); lines.push(""); if (include.code) { for (const variant of fragment.variants) { if (variant.code) { lines.push(`*${variant.name}:*`); lines.push("```tsx"); lines.push(variant.code); lines.push("```"); lines.push(""); } } } } if (include.relations && fragment.relations && fragment.relations.length > 0) { lines.push("**Related:**"); for (const relation of fragment.relations) { lines.push(`- ${relation.component} (${relation.relationship}): ${relation.note}`); } lines.push(""); } lines.push("---"); lines.push(""); } if (blocks && blocks.length > 0) { lines.push("## Blocks"); lines.push(""); lines.push("Composition patterns showing how components wire together."); lines.push(""); for (const block of blocks) { lines.push(`### ${block.name}`); lines.push(""); lines.push(block.description); lines.push(""); lines.push(`**Category:** ${block.category}`); lines.push(`**Components:** ${block.components.join(", ")}`); if (block.tags && block.tags.length > 0) { lines.push(`**Tags:** ${block.tags.join(", ")}`); } lines.push(""); lines.push("```tsx"); lines.push(block.code); lines.push("```"); lines.push(""); lines.push("---"); lines.push(""); } } const content = lines.join("\n"); return { content, tokenEstimate: estimateTokens(content) }; } function generateJsonContext( fragments: CompiledFragment[], include: Required>, compact: boolean, blocks?: CompiledBlock[] ): ContextResult { const categories = [...new Set(fragments.map((s) => s.meta.category))].sort(); interface JsonComponent { category: string; description: string; status?: string; whenToUse?: string[]; whenNotToUse?: string[]; composition?: { pattern: string; subComponents?: string[]; requiredChildren?: string[]; commonPatterns?: string[]; }; propsSummary?: string[]; compoundChildren?: Record; canonicalUsage?: string[]; props?: Record; variants?: string[]; relations?: Array<{ component: string; relationship: string; note: string }>; a11yRules?: string[]; bans?: Array<{ pattern: string; message: string }>; } const components: Record = {}; for (const fragment of fragments) { const component: JsonComponent = { category: fragment.meta.category, description: fragment.meta.description, }; if (fragment.meta.status) { component.status = fragment.meta.status; } if (!compact) { if (include.usage) { const whenFiltered = filterPlaceholders(fragment.usage.when); const whenNotFiltered = filterPlaceholders(fragment.usage.whenNot); if (whenFiltered.length > 0) component.whenToUse = whenFiltered; if (whenNotFiltered.length > 0) component.whenNotToUse = whenNotFiltered; } // Composition data if (fragment.ai?.compositionPattern) { const ai = fragment.ai; const comp: NonNullable = { pattern: ai.compositionPattern! }; if (ai.subComponents && ai.subComponents.length > 0) { comp.subComponents = ai.subComponents; } if (ai.requiredChildren && ai.requiredChildren.length > 0) { comp.requiredChildren = ai.requiredChildren; } if (ai.commonPatterns && ai.commonPatterns.length > 0) { comp.commonPatterns = ai.commonPatterns; } component.composition = comp; } // Contract data if (fragment.contract?.propsSummary && fragment.contract.propsSummary.length > 0) { component.propsSummary = fragment.contract.propsSummary; } if (fragment.contract?.compoundChildren && Object.keys(fragment.contract.compoundChildren).length > 0) { component.compoundChildren = fragment.contract.compoundChildren; } if (fragment.contract?.canonicalUsage && fragment.contract.canonicalUsage.length > 0) { component.canonicalUsage = fragment.contract.canonicalUsage; } if (fragment.contract?.a11yRules && fragment.contract.a11yRules.length > 0) { component.a11yRules = fragment.contract.a11yRules; } if (fragment.contract?.bans && fragment.contract.bans.length > 0) { component.bans = fragment.contract.bans; } if (include.props && Object.keys(fragment.props).length > 0) { component.props = {}; for (const [name, prop] of Object.entries(fragment.props)) { component.props[name] = { type: formatPropType(prop), description: prop.description, }; if (prop.required) component.props[name].required = true; if (prop.default !== undefined) component.props[name].default = prop.default; } } if (include.variants && fragment.variants.length > 0) { component.variants = fragment.variants.map((v) => v.name); } if (include.relations && fragment.relations && fragment.relations.length > 0) { component.relations = fragment.relations.map((r) => ({ component: r.component, relationship: r.relationship, note: r.note, })); } } components[fragment.meta.name] = component; } const blocksMap = blocks && blocks.length > 0 ? Object.fromEntries(blocks.map(b => [b.name, { description: b.description, category: b.category, components: b.components, code: b.code, tags: b.tags, }])) : undefined; const output = { version: "1.0", generatedAt: new Date().toISOString(), summary: { totalComponents: fragments.length, categories, ...(blocksMap && { totalBlocks: blocks!.length }), }, components, ...(blocksMap && { blocks: blocksMap }), }; const content = JSON.stringify(output, null, 2); return { content, tokenEstimate: estimateTokens(content) }; } function formatPropType(prop: PropDefinition): string { if (prop.type === "enum" && prop.values) { return prop.values.map((v) => `"${v}"`).join(" | "); } if (prop.default !== undefined) { return `${prop.type} (default: ${JSON.stringify(prop.default)})`; } return prop.type; } function truncate(str: string, maxLength: number): string { if (str.length <= maxLength) return str; return str.slice(0, maxLength - 3) + "..."; } function estimateTokens(text: string): number { return Math.ceil(text.length / 4); }