diff --git a/src/services/rules-builder/rules-generation-strategies/MultiFileRulesStrategy.ts b/src/services/rules-builder/rules-generation-strategies/MultiFileRulesStrategy.ts index 124b632..ee7f2dd 100644 --- a/src/services/rules-builder/rules-generation-strategies/MultiFileRulesStrategy.ts +++ b/src/services/rules-builder/rules-generation-strategies/MultiFileRulesStrategy.ts @@ -1,8 +1,14 @@ import type { RulesGenerationStrategy } from '../RulesGenerationStrategy.ts'; import { Layer, type Library, Stack } from '../../../data/dictionaries.ts'; import type { RulesContent } from '../RulesBuilderTypes.ts'; -import { getRulesForLibrary } from '../../../data/rules'; import { slugify } from '../../../utils/slugify.ts'; +import { + createProjectMarkdown, + createEmptyStateMarkdown, + generateLibraryContent, + renderLibrarySection, + PROJECT_FILE_CONFIG, +} from './shared/rulesMarkdownBuilders.ts'; /** * Strategy for multi-file rules generation @@ -15,17 +21,17 @@ export class MultiFileRulesStrategy implements RulesGenerationStrategy { stacksByLayer: Record, librariesByStack: Record, ): RulesContent[] { - const projectMarkdown = `# AI Rules for ${projectName}\n\n${projectDescription}\n\n`; - const noSelectedLibrariesMarkdown = `---\n\nšŸ‘ˆ Use the Rule Builder on the left or drop dependency file here`; - const projectLabel = 'Project', - projectFileName = 'project.mdc'; - + const projectMarkdown = createProjectMarkdown(projectName, projectDescription); const markdowns: RulesContent[] = []; - markdowns.push({ markdown: projectMarkdown, label: projectLabel, fileName: projectFileName }); + markdowns.push({ + markdown: projectMarkdown, + label: PROJECT_FILE_CONFIG.label, + fileName: PROJECT_FILE_CONFIG.fileName, + }); if (selectedLibraries.length === 0) { - markdowns[0].markdown += noSelectedLibrariesMarkdown; + markdowns[0].markdown += createEmptyStateMarkdown(); return markdowns; } @@ -37,7 +43,6 @@ export class MultiFileRulesStrategy implements RulesGenerationStrategy { layer, stack, library, - libraryRules: getRulesForLibrary(library), }), ); }); @@ -48,39 +53,18 @@ export class MultiFileRulesStrategy implements RulesGenerationStrategy { } private buildRulesContent({ - libraryRules, layer, stack, library, }: { - libraryRules: string[]; layer: string; stack: string; library: string; }): RulesContent { const label = `${layer} - ${stack} - ${library}`; const fileName: RulesContent['fileName'] = `${slugify(`${layer}-${stack}-${library}`)}.mdc`; - const content = - libraryRules.length > 0 - ? `${libraryRules.map((rule) => `- ${rule}`).join('\n')}` - : `- Use ${library} according to best practices`; - const markdown = this.renderRuleMarkdown({ content, layer, stack, library }); + const content = generateLibraryContent(library as Library); + const markdown = renderLibrarySection({ layer, stack, library, content }); return { markdown, label, fileName }; } - - private renderRuleMarkdown = ({ - content, - layer, - stack, - library, - }: { - content: string; - layer: string; - stack: string; - library: string; - }) => - `## ${layer}\n\n### Guidelines for ${stack}\n\n#### ${library}\n\n{{content}}\n\n`.replace( - '{{content}}', - content, - ); } diff --git a/src/services/rules-builder/rules-generation-strategies/SingleFileRulesStrategy.ts b/src/services/rules-builder/rules-generation-strategies/SingleFileRulesStrategy.ts index 467c9e7..95a98ef 100644 --- a/src/services/rules-builder/rules-generation-strategies/SingleFileRulesStrategy.ts +++ b/src/services/rules-builder/rules-generation-strategies/SingleFileRulesStrategy.ts @@ -1,7 +1,12 @@ import type { RulesGenerationStrategy } from '../RulesGenerationStrategy.ts'; import { Layer, Library, Stack } from '../../../data/dictionaries.ts'; import type { RulesContent } from '../RulesBuilderTypes.ts'; -import { getRulesForLibrary } from '../../../data/rules.ts'; +import { + createProjectMarkdown, + createEmptyStateMarkdown, + generateLibrarySections, + PROJECT_FILE_CONFIG, +} from './shared/rulesMarkdownBuilders.ts'; /** * Strategy for single-file rules generation @@ -14,58 +19,20 @@ export class SingleFileRulesStrategy implements RulesGenerationStrategy { stacksByLayer: Record, librariesByStack: Record, ): RulesContent[] { - const projectMarkdown = `# AI Rules for ${projectName}\n\n${projectDescription}\n\n`; - const noSelectedLibrariesMarkdown = `---\n\nšŸ‘ˆ Use the Rule Builder on the left or drop dependency file here`; - const projectLabel = 'Project', - projectFileName = 'project.mdc'; - - let markdown = projectMarkdown; + let markdown = createProjectMarkdown(projectName, projectDescription); if (selectedLibraries.length === 0) { - markdown += noSelectedLibrariesMarkdown; - return [{ markdown, label: projectLabel, fileName: projectFileName }]; + markdown += createEmptyStateMarkdown(); + return [ + { + markdown, + label: PROJECT_FILE_CONFIG.label, + fileName: PROJECT_FILE_CONFIG.fileName, + }, + ]; } - markdown += this.generateLibraryMarkdown(stacksByLayer, librariesByStack); + markdown += generateLibrarySections(stacksByLayer, librariesByStack); return [{ markdown, label: 'All Rules', fileName: 'rules.mdc' }]; } - - private generateLibraryMarkdown( - stacksByLayer: Record, - librariesByStack: Record, - ): string { - let markdown = ''; - - // Generate content for each layer and its stacks - Object.entries(stacksByLayer).forEach(([layer, stacks]) => { - markdown += `## ${layer}\n\n`; - - stacks.forEach((stack) => { - markdown += `### Guidelines for ${stack}\n\n`; - - const libraries = librariesByStack[stack]; - if (libraries) { - libraries.forEach((library) => { - markdown += `#### ${library}\n\n`; - - // Get specific rules for this library - const libraryRules = getRulesForLibrary(library); - if (libraryRules.length > 0) { - libraryRules.forEach((rule) => { - markdown += `- ${rule}\n`; - }); - } else { - markdown += `- Use ${library} according to best practices\n`; - } - - markdown += '\n'; - }); - } - - markdown += '\n'; - }); - }); - - return markdown; - } } diff --git a/src/services/rules-builder/rules-generation-strategies/shared/rulesMarkdownBuilders.ts b/src/services/rules-builder/rules-generation-strategies/shared/rulesMarkdownBuilders.ts new file mode 100644 index 0000000..0f30afe --- /dev/null +++ b/src/services/rules-builder/rules-generation-strategies/shared/rulesMarkdownBuilders.ts @@ -0,0 +1,87 @@ +import type { Layer, Library, Stack } from '../../../../data/dictionaries.ts'; +import { getRulesForLibrary } from '../../../../data/rules.ts'; + +/** + * Creates the project header markdown + */ +export function createProjectMarkdown(projectName: string, projectDescription: string): string { + return `# AI Rules for ${projectName}\n\n${projectDescription}\n\n`; +} + +/** + * Creates the empty state markdown + */ +export function createEmptyStateMarkdown(): string { + return `---\n\nšŸ‘ˆ Use the Rule Builder on the left or drop dependency file here`; +} + +/** + * Generates markdown for a single library + */ +export function generateLibraryContent(library: Library): string { + const libraryRules = getRulesForLibrary(library); + + if (libraryRules.length > 0) { + return libraryRules.map((rule) => `- ${rule}`).join('\n'); + } + + return `- Use ${library} according to best practices`; +} + +/** + * Renders a complete library section with headers + */ +export function renderLibrarySection({ + layer, + stack, + library, + content, +}: { + layer: string; + stack: string; + library: string; + content?: string; +}): string { + const finalContent = content ?? generateLibraryContent(library as Library); + + return `## ${layer}\n\n### Guidelines for ${stack}\n\n#### ${library}\n\n${finalContent}\n\n`; +} + +/** + * Generates markdown for all libraries organized by layer and stack + */ +export function generateLibrarySections( + stacksByLayer: Record, + librariesByStack: Record, +): string { + let markdown = ''; + + Object.entries(stacksByLayer).forEach(([layer, stacks]) => { + markdown += `## ${layer}\n\n`; + + stacks.forEach((stack) => { + markdown += `### Guidelines for ${stack}\n\n`; + + const libraries = librariesByStack[stack]; + if (libraries) { + libraries.forEach((library) => { + markdown += `#### ${library}\n\n`; + markdown += generateLibraryContent(library); + markdown += '\n\n'; + }); + } + + markdown += '\n'; + }); + }); + + return markdown; +} + +/** + * Default project file configuration + */ +export const PROJECT_FILE_CONFIG = { + label: 'Project', + fileName: 'project.mdc', +} as const;