mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-20 01:56:37 +03:00
Consolidate skills + add patterns source file
Major changes: - Consolidate 8 design skills into single frontend-design skill with 7 reference files - Add source/patterns.md as single source of truth for patterns/antipatterns - Patterns are merged into skill during build, served via API for website - Website now dynamically renders both "What TO Do" and "What NOT to Do" sections - Update build system to handle directory-based skills with references - Add /api/patterns endpoint to server - Refactor website with new Antidote section layout 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.5
parent
99f1091f20
commit
2181137d04
+9
-8
@@ -12,7 +12,7 @@
|
||||
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { readSourceFiles } from './lib/utils.js';
|
||||
import { readSourceFiles, readPatterns } from './lib/utils.js';
|
||||
import {
|
||||
transformCursor,
|
||||
transformClaudeCode,
|
||||
@@ -31,16 +31,17 @@ const DIST_DIR = path.join(ROOT_DIR, 'dist');
|
||||
*/
|
||||
async function build() {
|
||||
console.log('🔨 Building cross-provider design plugins...\n');
|
||||
|
||||
|
||||
// Read source files
|
||||
const { commands, skills } = readSourceFiles(ROOT_DIR);
|
||||
console.log(`📖 Read ${commands.length} commands and ${skills.length} skills\n`);
|
||||
|
||||
const patterns = readPatterns(ROOT_DIR);
|
||||
console.log(`📖 Read ${commands.length} commands, ${skills.length} skills, and ${patterns.patterns.length + patterns.antipatterns.length} pattern categories\n`);
|
||||
|
||||
// Transform for each provider
|
||||
transformCursor(commands, skills, DIST_DIR);
|
||||
transformClaudeCode(commands, skills, DIST_DIR);
|
||||
transformGemini(commands, skills, DIST_DIR);
|
||||
transformCodex(commands, skills, DIST_DIR);
|
||||
transformCursor(commands, skills, DIST_DIR, patterns);
|
||||
transformClaudeCode(commands, skills, DIST_DIR, patterns);
|
||||
transformGemini(commands, skills, DIST_DIR, patterns);
|
||||
transformCodex(commands, skills, DIST_DIR, patterns);
|
||||
|
||||
// Create ZIP bundles
|
||||
await createAllZips(DIST_DIR);
|
||||
|
||||
@@ -3,19 +3,20 @@ import { cleanDir, ensureDir, writeFile, generateYamlFrontmatter } from '../util
|
||||
|
||||
/**
|
||||
* Claude Code Transformer (Full Featured)
|
||||
*
|
||||
*
|
||||
* Keeps full YAML frontmatter with args support.
|
||||
* Skills stored in subdirectories with SKILL.md filename.
|
||||
* Supports reference files in skill subdirectories.
|
||||
*/
|
||||
export function transformClaudeCode(commands, skills, distDir) {
|
||||
export function transformClaudeCode(commands, skills, distDir, patterns = null) {
|
||||
const claudeDir = path.join(distDir, 'claude-code');
|
||||
const commandsDir = path.join(claudeDir, '.claude/commands');
|
||||
const skillsDir = path.join(claudeDir, '.claude/skills');
|
||||
|
||||
|
||||
cleanDir(claudeDir);
|
||||
ensureDir(commandsDir);
|
||||
ensureDir(skillsDir);
|
||||
|
||||
|
||||
// Commands: Keep frontmatter + body
|
||||
for (const command of commands) {
|
||||
const frontmatter = generateYamlFrontmatter({
|
||||
@@ -23,27 +24,52 @@ export function transformClaudeCode(commands, skills, distDir) {
|
||||
description: command.description,
|
||||
...(command.args.length > 0 && { args: command.args })
|
||||
});
|
||||
|
||||
|
||||
const content = `${frontmatter}\n\n${command.body}`;
|
||||
const outputPath = path.join(commandsDir, `${command.name}.md`);
|
||||
writeFile(outputPath, content);
|
||||
}
|
||||
|
||||
|
||||
// Skills: Keep frontmatter + body in subdirectories
|
||||
let refCount = 0;
|
||||
for (const skill of skills) {
|
||||
const skillDir = path.join(skillsDir, skill.name);
|
||||
|
||||
|
||||
const frontmatter = generateYamlFrontmatter({
|
||||
name: skill.name,
|
||||
description: skill.description,
|
||||
...(skill.license && { license: skill.license })
|
||||
});
|
||||
|
||||
const content = `${frontmatter}\n\n${skill.body}`;
|
||||
|
||||
let body = skill.body;
|
||||
|
||||
// Merge patterns body into frontend-design skill (before Domain Reference Files section)
|
||||
if (skill.name === 'frontend-design' && patterns && patterns.body) {
|
||||
const insertPoint = body.indexOf('---\n\n## Domain Reference Files');
|
||||
if (insertPoint > -1) {
|
||||
body = body.slice(0, insertPoint) + '\n\n' + patterns.body + '\n\n' + body.slice(insertPoint);
|
||||
} else {
|
||||
body += '\n\n' + patterns.body;
|
||||
}
|
||||
}
|
||||
|
||||
const content = `${frontmatter}\n\n${body}`;
|
||||
const outputPath = path.join(skillDir, 'SKILL.md');
|
||||
writeFile(outputPath, content);
|
||||
|
||||
// Copy reference files if they exist
|
||||
if (skill.references && skill.references.length > 0) {
|
||||
const refDir = path.join(skillDir, 'reference');
|
||||
ensureDir(refDir);
|
||||
for (const ref of skill.references) {
|
||||
const refOutputPath = path.join(refDir, `${ref.name}.md`);
|
||||
writeFile(refOutputPath, ref.content);
|
||||
refCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`✓ Claude Code: ${commands.length} commands, ${skills.length} skills`);
|
||||
|
||||
const refInfo = refCount > 0 ? ` (${refCount} reference files)` : '';
|
||||
console.log(`✓ Claude Code: ${commands.length} commands, ${skills.length} skills${refInfo}`);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,22 +3,23 @@ import { cleanDir, ensureDir, writeFile } from '../utils.js';
|
||||
|
||||
/**
|
||||
* Codex Transformer (Full Featured - Custom Prompts + Modular Skills)
|
||||
*
|
||||
*
|
||||
* Commands: Uses argument-hint format with $VARIABLE placeholders
|
||||
* Skills: Creates modular files with guiding AGENTS.md
|
||||
* Reference files are inlined into the main skill file for Codex
|
||||
*/
|
||||
export function transformCodex(commands, skills, distDir) {
|
||||
export function transformCodex(commands, skills, distDir, patterns = null) {
|
||||
const codexDir = path.join(distDir, 'codex');
|
||||
const promptsDir = path.join(codexDir, '.codex/prompts');
|
||||
|
||||
|
||||
cleanDir(codexDir);
|
||||
ensureDir(promptsDir);
|
||||
|
||||
|
||||
// Commands: Transform to Codex prompt format
|
||||
for (const command of commands) {
|
||||
const yamlLines = ['---'];
|
||||
yamlLines.push(`description: ${command.description}`);
|
||||
|
||||
|
||||
// Build argument-hint from args array
|
||||
if (command.args && command.args.length > 0) {
|
||||
const hints = command.args.map(arg => {
|
||||
@@ -27,32 +28,54 @@ export function transformCodex(commands, skills, distDir) {
|
||||
});
|
||||
yamlLines.push(`argument-hint: ${hints.join(' ')}`);
|
||||
}
|
||||
|
||||
|
||||
yamlLines.push('---');
|
||||
|
||||
|
||||
// Transform {{argname}} to $ARGNAME for Codex
|
||||
let body = command.body;
|
||||
body = body.replace(/\{\{([^}]+)\}\}/g, (match, argName) => {
|
||||
return `$${argName.toUpperCase()}`;
|
||||
});
|
||||
|
||||
|
||||
const content = `${yamlLines.join('\n')}\n\n${body}`;
|
||||
const outputPath = path.join(promptsDir, `${command.name}.md`);
|
||||
writeFile(outputPath, content);
|
||||
}
|
||||
|
||||
// Skills: Create modular files (body only)
|
||||
|
||||
// Skills: Create modular files (with references inlined)
|
||||
const skillEntries = [];
|
||||
let refCount = 0;
|
||||
for (const skill of skills) {
|
||||
let content = skill.body;
|
||||
|
||||
// Merge patterns body into frontend-design skill (before Domain Reference Files section)
|
||||
if (skill.name === 'frontend-design' && patterns && patterns.body) {
|
||||
const insertPoint = content.indexOf('---\n\n## Domain Reference Files');
|
||||
if (insertPoint > -1) {
|
||||
content = content.slice(0, insertPoint) + '\n\n' + patterns.body + '\n\n' + content.slice(insertPoint);
|
||||
} else {
|
||||
content += '\n\n' + patterns.body;
|
||||
}
|
||||
}
|
||||
|
||||
// Inline reference files if they exist
|
||||
if (skill.references && skill.references.length > 0) {
|
||||
const refSections = skill.references.map(ref => {
|
||||
refCount++;
|
||||
return `\n\n---\n\n## Reference: ${ref.name}\n\n${ref.content}`;
|
||||
});
|
||||
content += refSections.join('');
|
||||
}
|
||||
|
||||
const outputPath = path.join(codexDir, `AGENTS.${skill.name}.md`);
|
||||
writeFile(outputPath, skill.body);
|
||||
writeFile(outputPath, content);
|
||||
skillEntries.push({
|
||||
name: skill.name,
|
||||
description: skill.description,
|
||||
file: `AGENTS.${skill.name}.md`
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Create main AGENTS.md that guides Codex to the right skill files
|
||||
const agentsMd = [
|
||||
'# Codex Agent Instructions',
|
||||
@@ -63,7 +86,7 @@ export function transformCodex(commands, skills, distDir) {
|
||||
'',
|
||||
'Each skill provides deep expertise in its domain. Use the descriptions below to decide which skill file to read:',
|
||||
'',
|
||||
...skillEntries.map(skill =>
|
||||
...skillEntries.map(skill =>
|
||||
`### ${skill.name}\n\n**When to use**: ${skill.description}\n\n**Read**: \`${skill.file}\` for complete instructions.\n`
|
||||
),
|
||||
'',
|
||||
@@ -76,9 +99,10 @@ export function transformCodex(commands, skills, distDir) {
|
||||
'',
|
||||
'Multiple skills can be combined when the task requires expertise from different domains.'
|
||||
].join('\n');
|
||||
|
||||
|
||||
writeFile(path.join(codexDir, 'AGENTS.md'), agentsMd);
|
||||
|
||||
console.log(`✓ Codex: ${commands.length} prompts, ${skills.length} skills (modular)`);
|
||||
|
||||
const refInfo = refCount > 0 ? ` (${refCount} refs inlined)` : '';
|
||||
console.log(`✓ Codex: ${commands.length} prompts, ${skills.length} skills (modular)${refInfo}`);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,31 +3,55 @@ import { cleanDir, ensureDir, writeFile } from '../utils.js';
|
||||
|
||||
/**
|
||||
* Cursor Transformer (Downgraded - No Frontmatter/Args)
|
||||
*
|
||||
*
|
||||
* Strips all frontmatter and metadata, outputs body only.
|
||||
* Cursor doesn't support arguments or frontmatter.
|
||||
* Reference files are inlined into the main skill file.
|
||||
*/
|
||||
export function transformCursor(commands, skills, distDir) {
|
||||
export function transformCursor(commands, skills, distDir, patterns = null) {
|
||||
const cursorDir = path.join(distDir, 'cursor');
|
||||
const commandsDir = path.join(cursorDir, '.cursor/commands');
|
||||
const rulesDir = path.join(cursorDir, '.cursor/rules');
|
||||
|
||||
|
||||
cleanDir(cursorDir);
|
||||
ensureDir(commandsDir);
|
||||
ensureDir(rulesDir);
|
||||
|
||||
|
||||
// Commands: Body only (no frontmatter)
|
||||
for (const command of commands) {
|
||||
const outputPath = path.join(commandsDir, `${command.name}.md`);
|
||||
writeFile(outputPath, command.body);
|
||||
}
|
||||
|
||||
// Skills: Body only (no frontmatter)
|
||||
|
||||
// Skills: Body only (with references inlined)
|
||||
let refCount = 0;
|
||||
for (const skill of skills) {
|
||||
let content = skill.body;
|
||||
|
||||
// Merge patterns body into frontend-design skill (before Domain Reference Files section)
|
||||
if (skill.name === 'frontend-design' && patterns && patterns.body) {
|
||||
const insertPoint = content.indexOf('---\n\n## Domain Reference Files');
|
||||
if (insertPoint > -1) {
|
||||
content = content.slice(0, insertPoint) + '\n\n' + patterns.body + '\n\n' + content.slice(insertPoint);
|
||||
} else {
|
||||
content += '\n\n' + patterns.body;
|
||||
}
|
||||
}
|
||||
|
||||
// Inline reference files if they exist
|
||||
if (skill.references && skill.references.length > 0) {
|
||||
const refSections = skill.references.map(ref => {
|
||||
refCount++;
|
||||
return `\n\n---\n\n## Reference: ${ref.name}\n\n${ref.content}`;
|
||||
});
|
||||
content += refSections.join('');
|
||||
}
|
||||
|
||||
const outputPath = path.join(rulesDir, `${skill.name}.md`);
|
||||
writeFile(outputPath, skill.body);
|
||||
writeFile(outputPath, content);
|
||||
}
|
||||
|
||||
console.log(`✓ Cursor: ${commands.length} commands, ${skills.length} skills (downgraded)`);
|
||||
|
||||
const refInfo = refCount > 0 ? ` (${refCount} refs inlined)` : '';
|
||||
console.log(`✓ Cursor: ${commands.length} commands, ${skills.length} skills (downgraded)${refInfo}`);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,39 +3,62 @@ import { cleanDir, ensureDir, writeFile } from '../utils.js';
|
||||
|
||||
/**
|
||||
* Gemini Transformer (Full Featured - TOML + Modular Skills)
|
||||
*
|
||||
*
|
||||
* Commands: Converts to TOML format with {{args}} placeholders
|
||||
* Skills: Creates modular files imported via @./GEMINI.{name}.md syntax
|
||||
* Reference files are inlined into the main skill file for Gemini
|
||||
*/
|
||||
export function transformGemini(commands, skills, distDir) {
|
||||
export function transformGemini(commands, skills, distDir, patterns = null) {
|
||||
const geminiDir = path.join(distDir, 'gemini');
|
||||
const commandsDir = path.join(geminiDir, '.gemini/commands');
|
||||
|
||||
|
||||
cleanDir(geminiDir);
|
||||
ensureDir(commandsDir);
|
||||
|
||||
|
||||
// Commands: Transform to TOML
|
||||
for (const command of commands) {
|
||||
// Replace named placeholders with {{args}}
|
||||
let prompt = command.body.replace(/\{\{[^}]+\}\}/g, '{{args}}');
|
||||
|
||||
|
||||
const toml = [
|
||||
`description = "${command.description.replace(/"/g, '\\"')}"`,
|
||||
`prompt = """`,
|
||||
prompt,
|
||||
`"""`
|
||||
].join('\n');
|
||||
|
||||
|
||||
const outputPath = path.join(commandsDir, `${command.name}.toml`);
|
||||
writeFile(outputPath, toml);
|
||||
}
|
||||
|
||||
// Skills: Create modular files
|
||||
|
||||
// Skills: Create modular files (with references inlined)
|
||||
let refCount = 0;
|
||||
for (const skill of skills) {
|
||||
let content = skill.body;
|
||||
|
||||
// Merge patterns body into frontend-design skill (before Domain Reference Files section)
|
||||
if (skill.name === 'frontend-design' && patterns && patterns.body) {
|
||||
const insertPoint = content.indexOf('---\n\n## Domain Reference Files');
|
||||
if (insertPoint > -1) {
|
||||
content = content.slice(0, insertPoint) + '\n\n' + patterns.body + '\n\n' + content.slice(insertPoint);
|
||||
} else {
|
||||
content += '\n\n' + patterns.body;
|
||||
}
|
||||
}
|
||||
|
||||
// Inline reference files if they exist
|
||||
if (skill.references && skill.references.length > 0) {
|
||||
const refSections = skill.references.map(ref => {
|
||||
refCount++;
|
||||
return `\n\n---\n\n## Reference: ${ref.name}\n\n${ref.content}`;
|
||||
});
|
||||
content += refSections.join('');
|
||||
}
|
||||
|
||||
const outputPath = path.join(geminiDir, `GEMINI.${skill.name}.md`);
|
||||
writeFile(outputPath, skill.body);
|
||||
writeFile(outputPath, content);
|
||||
}
|
||||
|
||||
|
||||
// Create main GEMINI.md that imports skill files
|
||||
const geminiMd = [
|
||||
'# Gemini Context',
|
||||
@@ -46,7 +69,7 @@ export function transformGemini(commands, skills, distDir) {
|
||||
'',
|
||||
'Each skill provides deep expertise in its domain. The skills below are automatically imported and will guide your responses:',
|
||||
'',
|
||||
...skills.map(skill =>
|
||||
...skills.map(skill =>
|
||||
`### ${skill.name}\n\n**When to use**: ${skill.description}\n\n@./GEMINI.${skill.name}.md\n`
|
||||
),
|
||||
'',
|
||||
@@ -57,9 +80,10 @@ export function transformGemini(commands, skills, distDir) {
|
||||
'3. Multiple skills can be combined when the task requires expertise from different domains',
|
||||
'4. Follow the detailed instructions provided in each imported skill file'
|
||||
].join('\n');
|
||||
|
||||
|
||||
writeFile(path.join(geminiDir, 'GEMINI.md'), geminiMd);
|
||||
|
||||
console.log(`✓ Gemini: ${commands.length} commands (TOML), ${skills.length} skills (modular)`);
|
||||
|
||||
const refInfo = refCount > 0 ? ` (${refCount} refs inlined)` : '';
|
||||
console.log(`✓ Gemini: ${commands.length} commands (TOML), ${skills.length} skills (modular)${refInfo}`);
|
||||
}
|
||||
|
||||
|
||||
+147
-19
@@ -103,19 +103,21 @@ export function readFilesRecursive(dir, fileList = []) {
|
||||
|
||||
/**
|
||||
* Read and parse all source files
|
||||
* Supports both:
|
||||
* - Single file skills: source/skills/{name}.md
|
||||
* - Directory skills: source/skills/{name}/SKILL.md + reference/*.md
|
||||
*/
|
||||
export function readSourceFiles(rootDir) {
|
||||
const commandsDir = path.join(rootDir, 'source/commands');
|
||||
const skillsDir = path.join(rootDir, 'source/skills');
|
||||
|
||||
|
||||
const commandFiles = readFilesRecursive(commandsDir);
|
||||
const skillFiles = readFilesRecursive(skillsDir);
|
||||
|
||||
|
||||
const commands = commandFiles.map(filePath => {
|
||||
const content = fs.readFileSync(filePath, 'utf-8');
|
||||
const { frontmatter, body } = parseFrontmatter(content);
|
||||
const name = path.basename(filePath, '.md');
|
||||
|
||||
|
||||
return {
|
||||
name: frontmatter.name || name,
|
||||
description: frontmatter.description || '',
|
||||
@@ -124,21 +126,66 @@ export function readSourceFiles(rootDir) {
|
||||
filePath
|
||||
};
|
||||
});
|
||||
|
||||
const skills = skillFiles.map(filePath => {
|
||||
const content = fs.readFileSync(filePath, 'utf-8');
|
||||
const { frontmatter, body } = parseFrontmatter(content);
|
||||
const name = path.basename(filePath, '.md');
|
||||
|
||||
return {
|
||||
name: frontmatter.name || name,
|
||||
description: frontmatter.description || '',
|
||||
license: frontmatter.license || '',
|
||||
body,
|
||||
filePath
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
// Read skills - handling both file and directory formats
|
||||
const skills = [];
|
||||
|
||||
if (fs.existsSync(skillsDir)) {
|
||||
const entries = fs.readdirSync(skillsDir, { withFileTypes: true });
|
||||
|
||||
for (const entry of entries) {
|
||||
const entryPath = path.join(skillsDir, entry.name);
|
||||
|
||||
if (entry.isDirectory()) {
|
||||
// Directory-based skill with potential references
|
||||
const skillMdPath = path.join(entryPath, 'SKILL.md');
|
||||
if (fs.existsSync(skillMdPath)) {
|
||||
const content = fs.readFileSync(skillMdPath, 'utf-8');
|
||||
const { frontmatter, body } = parseFrontmatter(content);
|
||||
|
||||
// Read reference files if they exist
|
||||
const references = [];
|
||||
const referenceDir = path.join(entryPath, 'reference');
|
||||
if (fs.existsSync(referenceDir)) {
|
||||
const refFiles = fs.readdirSync(referenceDir).filter(f => f.endsWith('.md'));
|
||||
for (const refFile of refFiles) {
|
||||
const refPath = path.join(referenceDir, refFile);
|
||||
const refContent = fs.readFileSync(refPath, 'utf-8');
|
||||
references.push({
|
||||
name: path.basename(refFile, '.md'),
|
||||
content: refContent,
|
||||
filePath: refPath
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
skills.push({
|
||||
name: frontmatter.name || entry.name,
|
||||
description: frontmatter.description || '',
|
||||
license: frontmatter.license || '',
|
||||
body,
|
||||
filePath: skillMdPath,
|
||||
references
|
||||
});
|
||||
}
|
||||
} else if (entry.name.endsWith('.md')) {
|
||||
// Single file skill (legacy format)
|
||||
const content = fs.readFileSync(entryPath, 'utf-8');
|
||||
const { frontmatter, body } = parseFrontmatter(content);
|
||||
const name = path.basename(entry.name, '.md');
|
||||
|
||||
skills.push({
|
||||
name: frontmatter.name || name,
|
||||
description: frontmatter.description || '',
|
||||
license: frontmatter.license || '',
|
||||
body,
|
||||
filePath: entryPath,
|
||||
references: []
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { commands, skills };
|
||||
}
|
||||
|
||||
@@ -169,6 +216,87 @@ export function writeFile(filePath, content) {
|
||||
fs.writeFileSync(filePath, content, 'utf-8');
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and parse patterns.md
|
||||
* Returns { patterns: [...], antipatterns: [...], body: string }
|
||||
*/
|
||||
export function readPatterns(rootDir) {
|
||||
const filePath = path.join(rootDir, 'source/patterns.md');
|
||||
|
||||
if (!fs.existsSync(filePath)) {
|
||||
return { patterns: [], antipatterns: [], body: '' };
|
||||
}
|
||||
|
||||
const content = fs.readFileSync(filePath, 'utf-8');
|
||||
|
||||
// Split frontmatter and body
|
||||
const frontmatterRegex = /^---\n([\s\S]*?)\n---\n([\s\S]*)$/;
|
||||
const match = content.match(frontmatterRegex);
|
||||
|
||||
if (!match) {
|
||||
return { patterns: [], antipatterns: [], body: content };
|
||||
}
|
||||
|
||||
const [, frontmatterText, body] = match;
|
||||
|
||||
// Parse both patterns and antipatterns sections
|
||||
const patterns = [];
|
||||
const antipatterns = [];
|
||||
const lines = frontmatterText.split('\n');
|
||||
let currentSection = null; // 'patterns' or 'antipatterns'
|
||||
let currentCategory = null;
|
||||
let inItems = false;
|
||||
|
||||
for (const line of lines) {
|
||||
const trimmed = line.trim();
|
||||
if (!trimmed) continue;
|
||||
|
||||
const indent = line.length - line.trimStart().length;
|
||||
|
||||
// Top-level section declaration
|
||||
if (indent === 0 && trimmed === 'patterns:') {
|
||||
currentSection = 'patterns';
|
||||
currentCategory = null;
|
||||
inItems = false;
|
||||
continue;
|
||||
}
|
||||
if (indent === 0 && trimmed === 'antipatterns:') {
|
||||
currentSection = 'antipatterns';
|
||||
currentCategory = null;
|
||||
inItems = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
// New category starts with "- name:"
|
||||
if (trimmed.startsWith('- name:') && currentSection) {
|
||||
currentCategory = {
|
||||
name: trimmed.slice(7).trim(),
|
||||
items: []
|
||||
};
|
||||
if (currentSection === 'patterns') {
|
||||
patterns.push(currentCategory);
|
||||
} else {
|
||||
antipatterns.push(currentCategory);
|
||||
}
|
||||
inItems = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Items array declaration
|
||||
if (trimmed === 'items:' && currentCategory) {
|
||||
inItems = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Item within items array (indented with "- ")
|
||||
if (trimmed.startsWith('- ') && inItems && currentCategory && indent >= 6) {
|
||||
currentCategory.items.push(trimmed.slice(2).trim());
|
||||
}
|
||||
}
|
||||
|
||||
return { patterns, antipatterns, body: body.trim() };
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate YAML frontmatter string
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user