@vibe-dev-kit/cli
Version:
Advanced Command-line toolkit that analyzes your codebase and deploys project-aware rules, memories, commands and agents to any AI coding assistant - VDK is the world's first Vibe Development Kit
1,210 lines (994 loc) • 38.2 kB
JavaScript
/**
* RuleAdapter.js
*
* Transforms standardized VDK rules into IDE-native formats and locations.
* Each IDE gets rules in the format that makes the most sense for that platform.
*/
import chalk from 'chalk';
import fs from 'fs/promises';
import os from 'os';
import path from 'path';
export class RuleAdapter {
constructor(options = {}) {
this.verbose = options.verbose || false;
this.projectPath = options.projectPath || process.cwd();
// Centralized character limits for each IDE
this.characterLimits = {
windsurf: {
perFile: 6000,
totalWorkspace: 12000,
global: 6000,
},
'github-copilot': {
perGuideline: 600,
maxGuidelines: 6,
},
cursor: {
perFile: Infinity, // No specific limit
total: Infinity,
},
claude: {
perFile: Infinity, // No specific limit
perCommand: 10000, // Reasonable limit for commands
},
};
}
/**
* Adapt a collection of rules for a specific IDE
* @param {Array} rules - Array of rule objects with frontmatter and content
* @param {string} targetIDE - Target IDE ('claude', 'cursor', 'windsurf', 'github-copilot')
* @param {Object} projectContext - Project analysis context
* @returns {Object} Adapted rules with paths and content
*/
async adaptRules(rules, targetIDE, projectContext = {}) {
if (this.verbose) {
console.log(chalk.gray(`Adapting ${rules.length} rules for ${targetIDE}...`));
}
switch (targetIDE.toLowerCase()) {
case 'claude':
case 'claude-code':
return await this.adaptForClaude(rules, projectContext);
case 'cursor':
case 'cursor-ai':
return await this.adaptForCursor(rules, projectContext);
case 'windsurf':
return await this.adaptForWindsurf(rules, projectContext);
case 'github-copilot':
return await this.adaptForGitHubCopilot(rules, projectContext);
default:
throw new Error(`Unknown IDE: ${targetIDE}`);
}
}
/**
* Enforce character limits for content based on IDE type
* @param {string} content - Content to check/truncate
* @param {string} ideType - IDE type
* @param {string} limitType - Type of limit (perFile, perGuideline, etc.)
* @returns {Object} Result with content and truncation info
*/
enforceCharacterLimits(content, ideType, limitType = 'perFile') {
const limits = this.characterLimits[ideType.toLowerCase()];
if (!limits) {
return { content, truncated: false, originalLength: content.length };
}
const limit = limits[limitType];
if (limit === Infinity || content.length <= limit) {
return { content, truncated: false, originalLength: content.length };
}
// Smart truncation at sentence boundaries
const truncationPoint = this.findSmartTruncationPoint(content, limit);
const truncatedContent =
content.substring(0, truncationPoint) +
(truncationPoint < content.length ? '\n\n*Truncated due to character limit*' : '');
if (this.verbose) {
console.warn(
chalk.yellow(
`Content truncated for ${ideType} (${content.length} → ${truncatedContent.length} chars)`
)
);
}
return {
content: truncatedContent,
truncated: true,
originalLength: content.length,
truncatedLength: truncatedContent.length,
};
}
/**
* Find smart truncation point at sentence or paragraph boundaries
* @param {string} content - Content to truncate
* @param {number} limit - Character limit
* @returns {number} Truncation point
*/
findSmartTruncationPoint(content, limit) {
const buffer = Math.max(50, limit * 0.05); // 5% buffer or minimum 50 chars
const targetLength = limit - buffer;
if (content.length <= targetLength) {
return content.length;
}
// Try to find good truncation points in order of preference
const truncationPoints = [
content.lastIndexOf('\n\n', targetLength), // Paragraph boundary
content.lastIndexOf('.\n', targetLength), // Sentence with newline
content.lastIndexOf('. ', targetLength), // Sentence boundary
content.lastIndexOf('\n', targetLength), // Line boundary
targetLength, // Hard truncation
];
for (const point of truncationPoints) {
if (point > targetLength * 0.7) {
// Keep at least 70% of content
return point;
}
}
return targetLength; // Fallback to hard truncation
}
/**
* Adapt rules for Claude Code's memory system with proper hierarchy
* @param {Array} rules - Standardized rules
* @param {Object} projectContext - Project context
* @returns {Object} Claude-native memory files and commands
*/
async adaptForClaude(rules, projectContext) {
const adaptedFiles = [];
const projectName = path.basename(this.projectPath);
const globalDir = path.join(os.homedir(), '.claude');
const commandsDir = path.join(this.projectPath, '.claude', 'commands');
const userCommandsDir = path.join(os.homedir(), '.claude', 'commands');
// 1. Global memory (cross-project preferences) - Deploy to ~/.claude/CLAUDE.md
const globalRules = rules.filter(
(rule) => rule.frontmatter?.category === 'core' && rule.frontmatter?.alwaysApply === true
);
if (globalRules.length > 0) {
const globalContent = this.generateClaudeGlobalMemory(globalRules);
adaptedFiles.push({
path: path.join(globalDir, 'CLAUDE.md'),
content: globalContent,
type: 'memory',
scope: 'global',
hierarchyLevel: 'global',
});
}
// 2. Main project memory (team-shared conventions) - Deploy to ./CLAUDE.md
const coreRules = rules.filter(
(rule) =>
rule.frontmatter?.category === 'core' ||
(rule.frontmatter?.alwaysApply === true && rule.frontmatter?.category !== 'core')
);
if (coreRules.length > 0) {
const claudeMainContent = this.generateClaudeMainMemory(coreRules, projectContext);
adaptedFiles.push({
path: path.join(this.projectPath, 'CLAUDE.md'),
content: claudeMainContent,
type: 'memory',
scope: 'project',
hierarchyLevel: 'project',
});
}
// 3. Technology-specific memory files with import structure
const techRules = rules.filter(
(rule) =>
rule.frontmatter?.category === 'technology' ||
rule.frontmatter?.category === 'framework' ||
rule.frontmatter?.category === 'language'
);
if (techRules.length > 0) {
const claudeTechContent = this.generateClaudeTechMemory(techRules, projectContext);
adaptedFiles.push({
path: path.join(this.projectPath, 'CLAUDE-patterns.md'),
content: claudeTechContent,
type: 'memory',
scope: 'project',
hierarchyLevel: 'technology',
});
}
// 4. Personal preferences import file
const personalPrefsContent = this.generateClaudePersonalPrefsImport();
adaptedFiles.push({
path: path.join(this.projectPath, 'CLAUDE-personal.md'),
content: personalPrefsContent,
type: 'memory',
scope: 'import',
hierarchyLevel: 'personal',
});
// 5. Project commands (namespace: /project:)
const taskRules = rules.filter((rule) => rule.frontmatter?.category === 'task');
for (const rule of taskRules) {
const commandContent = this.generateClaudeSlashCommand(rule, projectContext);
const commandName = this.getCommandName(rule.frontmatter?.description);
adaptedFiles.push({
path: path.join(commandsDir, `${commandName}.md`),
content: commandContent,
type: 'command',
scope: 'project',
namespace: 'project',
commandName: commandName,
});
}
// 6. User commands (namespace: /user:) - Deploy to ~/.claude/commands/
const personalRules = rules.filter(
(rule) =>
rule.frontmatter?.category === 'assistant' || rule.frontmatter?.category === 'workflow'
);
for (const rule of personalRules) {
const commandContent = this.generateClaudeSlashCommand(rule, projectContext);
const commandName = this.getCommandName(rule.frontmatter?.description);
adaptedFiles.push({
path: path.join(userCommandsDir, `${commandName}.md`),
content: commandContent,
type: 'command',
scope: 'user',
namespace: 'user',
commandName: commandName,
});
}
return {
files: adaptedFiles,
summary: {
memoryFiles: adaptedFiles.filter((f) => f.type === 'memory').length,
commands: adaptedFiles.filter((f) => f.type === 'command').length,
totalSize: adaptedFiles.reduce((size, file) => size + file.content.length, 0),
},
};
}
/**
* Adapt rules for Cursor's MDC system with proper activation types
* @param {Array} rules - Standardized rules
* @param {Object} projectContext - Project context
* @returns {Object} Cursor-native MDC files
*/
async adaptForCursor(rules, projectContext) {
const adaptedFiles = [];
const rulesDir = path.join(this.projectPath, '.cursor', 'rules');
// Group rules by activation type for better organization
const rulesByActivation = this.groupCursorRulesByActivation(rules);
// 1. Always rules - Always included in context
for (const rule of rulesByActivation.always) {
const mdcContent = this.generateCursorMDC(rule, projectContext);
const fileName = this.getCursorFileName(rule);
adaptedFiles.push({
path: path.join(rulesDir, `always-${fileName}`),
content: mdcContent,
type: 'rule',
activation: 'always',
scope: 'project',
priority: 'high',
});
}
// 2. Auto-attached rules - When files matching globs are referenced
for (const rule of rulesByActivation.autoAttached) {
const mdcContent = this.generateCursorMDC(rule, projectContext);
const fileName = this.getCursorFileName(rule);
adaptedFiles.push({
path: path.join(rulesDir, `auto-${fileName}`),
content: mdcContent,
type: 'rule',
activation: 'auto-attached',
scope: 'project',
globs: rule.frontmatter?.globs || [],
priority: 'medium',
});
}
// 3. Agent-requested rules - AI decides whether to include
for (const rule of rulesByActivation.agentRequested) {
const mdcContent = this.generateCursorMDC(rule, projectContext);
const fileName = this.getCursorFileName(rule);
adaptedFiles.push({
path: path.join(rulesDir, `agent-${fileName}`),
content: mdcContent,
type: 'rule',
activation: 'agent-requested',
scope: 'project',
description: rule.frontmatter?.description,
priority: 'medium',
});
}
// 4. Manual rules - Only when explicitly mentioned using @ruleName
for (const rule of rulesByActivation.manual) {
const mdcContent = this.generateCursorMDC(rule, projectContext);
const fileName = this.getCursorFileName(rule);
const ruleName = this.getCursorRuleName(rule);
adaptedFiles.push({
path: path.join(rulesDir, `manual-${fileName}`),
content: mdcContent,
type: 'rule',
activation: 'manual',
scope: 'project',
ruleName: ruleName,
priority: 'low',
});
}
return {
files: adaptedFiles,
summary: {
always: rulesByActivation.always.length,
autoAttached: rulesByActivation.autoAttached.length,
agentRequested: rulesByActivation.agentRequested.length,
manual: rulesByActivation.manual.length,
totalFiles: adaptedFiles.length,
},
};
}
/**
* Generate Cursor MDC format with proper frontmatter (FIXED IMPLEMENTATION)
* @param {Object} rule - Rule object
* @param {Object} projectContext - Project context
* @returns {string} MDC formatted content
*/
generateCursorMDC(rule, projectContext) {
const activationType = this.getCursorActivationType(rule);
const globs = rule.frontmatter?.globs || [];
const description = rule.frontmatter?.description || '';
const cleanContent = this.stripFrontmatter(rule.content);
return `---
type: ${activationType}
${globs.length > 0 ? `globs: ${JSON.stringify(globs)}` : ''}
${activationType === 'agent-requested' ? `description: "${description}"` : ''}
alwaysApply: ${rule.frontmatter?.alwaysApply || false}
---
${cleanContent}`;
}
/**
* Get Cursor activation type (FIXED IMPLEMENTATION)
* @param {Object} rule - Rule object
* @returns {string} Activation type
*/
getCursorActivationType(rule) {
if (rule.frontmatter?.alwaysApply) return 'always';
if (rule.frontmatter?.globs && rule.frontmatter?.globs.length > 0) return 'auto-attached';
if (rule.frontmatter?.description) return 'agent-requested';
return 'manual';
}
/**
* Get Cursor file name (FIXED IMPLEMENTATION)
* @param {Object} rule - Rule object
* @returns {string} File name
*/
getCursorFileName(rule) {
const category = rule.frontmatter?.category || 'general';
const framework = rule.frontmatter?.framework || '';
const description = rule.frontmatter?.description || '';
let baseName = framework || category;
if (description) {
baseName = description
.toLowerCase()
.replace(/[^a-z0-9\s]/g, '')
.replace(/\s+/g, '-');
}
return `${baseName.toLowerCase().replace(/[^a-z0-9-]/g, '')}.md`;
}
/**
* Group Cursor rules by activation type
* @param {Array} rules - Rules to group
* @returns {Object} Rules grouped by activation type
*/
groupCursorRulesByActivation(rules) {
const groups = {
always: [],
autoAttached: [],
agentRequested: [],
manual: [],
};
for (const rule of rules) {
const activationType = this.getCursorActivationType(rule);
const groupKey =
activationType === 'auto-attached'
? 'autoAttached'
: activationType === 'agent-requested'
? 'agentRequested'
: activationType;
if (groups[groupKey]) {
groups[groupKey].push(rule);
} else {
groups.manual.push(rule);
}
}
return groups;
}
/**
* Get Cursor rule name for manual activation
* @param {Object} rule - Rule object
* @returns {string} Rule name for @ruleName usage
*/
getCursorRuleName(rule) {
const description = rule.frontmatter?.description || '';
const category = rule.frontmatter?.category || 'rule';
if (description) {
return description
.toLowerCase()
.replace(/[^a-z0-9\s]/g, '')
.replace(/\s+/g, '')
.substring(0, 20);
}
return category.toLowerCase();
}
/**
* Adapt rules for Windsurf's memories system with proper deployment
* @param {Array} rules - Standardized rules
* @param {Object} projectContext - Project context
* @returns {Object} Windsurf-native memory files
*/
async adaptForWindsurf(rules, projectContext) {
const adaptedFiles = [];
const rulesDir = path.join(this.projectPath, '.windsurf', 'rules');
const globalDir = path.join(os.homedir(), '.codeium', 'windsurf', 'memories');
const projectRuleFile = path.join(this.projectPath, '.windsurfrules.md');
// 1. Global rules (organization-wide standards) - Deploy to ~/.codeium/windsurf/memories/
const globalRules = rules.filter(
(rule) => rule.frontmatter?.category === 'core' || rule.frontmatter?.alwaysApply === true
);
if (globalRules.length > 0) {
let globalContent = this.generateWindsurfGlobalMemory(globalRules, projectContext);
// Enforce 6K character limit for global rules
if (globalContent.length > 6000) {
if (this.verbose) {
console.warn(chalk.yellow(`Global Windsurf rules exceed 6K limit, truncating...`));
}
globalContent = globalContent.substring(0, 5900) + '\n\n*Truncated due to character limit*';
}
adaptedFiles.push({
path: path.join(globalDir, 'global_rules.md'),
content: globalContent,
type: 'memory',
scope: 'global',
characterCount: globalContent.length,
activationType: 'always-on',
});
}
// 2. Workspace rules (project-specific) - Deploy to .windsurf/rules/
const workspaceRules = rules.filter(
(rule) => rule.frontmatter?.category !== 'core' && rule.frontmatter?.alwaysApply !== true
);
for (const rule of workspaceRules) {
let windsurfContent = this.generateWindsurfRule(rule, projectContext);
// Ensure character limit compliance (6K per file)
if (windsurfContent.length > 6000) {
if (this.verbose) {
console.warn(
chalk.yellow(`Rule ${rule.frontmatter?.description} exceeds 6K limit, truncating...`)
);
}
windsurfContent =
windsurfContent.substring(0, 5900) + '\n\n*Truncated due to character limit*';
}
const fileName = this.getWindsurfFileName(rule);
const activationType = this.getWindsurfActivationType(rule);
adaptedFiles.push({
path: path.join(rulesDir, fileName),
content: windsurfContent,
type: 'rule',
scope: 'workspace',
characterCount: windsurfContent.length,
activationType: activationType,
});
}
// 3. Project rule file (.windsurfrules.md) - Alternative single-file approach
if (workspaceRules.length > 0) {
const consolidatedContent = this.generateConsolidatedWindsurfRules(
workspaceRules,
projectContext
);
// Check if consolidated approach is better (fewer files, under limits)
if (consolidatedContent.length <= 6000 && workspaceRules.length > 3) {
adaptedFiles.push({
path: projectRuleFile,
content: consolidatedContent,
type: 'project-rules',
scope: 'project',
characterCount: consolidatedContent.length,
activationType: 'model-decision',
});
}
}
// Ensure total character limit compliance (12K across all workspace rules)
const totalChars = adaptedFiles
.filter((f) => f.scope === 'workspace')
.reduce((total, file) => total + file.characterCount, 0);
if (totalChars > 12000) {
if (this.verbose) {
console.warn(
chalk.yellow(`⚠️ Windsurf workspace rules exceed 12K total limit (${totalChars} chars)`)
);
}
// Truncate files proportionally to stay under limit
const reductionRatio = 11000 / totalChars; // Leave some buffer
for (const file of adaptedFiles.filter((f) => f.scope === 'workspace')) {
const targetLength = Math.floor(file.characterCount * reductionRatio);
if (file.content.length > targetLength) {
file.content =
file.content.substring(0, targetLength - 50) + '\n\n*Truncated for total limit*';
file.characterCount = file.content.length;
}
}
}
return {
files: adaptedFiles,
summary: {
globalRules: adaptedFiles.filter((f) => f.scope === 'global').length,
workspaceRules: adaptedFiles.filter((f) => f.scope === 'workspace').length,
totalCharacters: adaptedFiles.reduce((total, file) => total + file.characterCount, 0),
characterLimit: 12000,
},
};
}
/**
* Adapt rules for GitHub Copilot's guidelines system (FIXED APPROACH)
* Generate setup instructions instead of files as per report findings
* @param {Array} rules - Standardized rules
* @param {Object} projectContext - Project context
* @returns {Object} GitHub Copilot setup instructions
*/
async adaptForGitHubCopilot(rules, projectContext) {
// Don't generate files - generate setup instructions
const prioritizedRules = this.prioritizeRulesForCopilot(rules);
const selectedRules = prioritizedRules.slice(0, 6); // GitHub Copilot Enterprise limit
const guidelines = selectedRules.map((rule, index) => ({
number: index + 1,
name: rule.frontmatter?.description || `Rule ${index + 1}`,
description: this.truncateToCharLimit(this.stripFrontmatter(rule.content), 600), // 600 char limit
filePatterns: rule.frontmatter?.globs || ['**/*'],
}));
const instructionsContent = `# GitHub Copilot Setup Instructions
## Configure in Repository Settings
⚠️ **Enterprise Feature**: Requires GitHub Copilot Enterprise plan
## Configuration Steps
1. Go to Settings → Code & automation → Copilot → Code review
2. Add these coding guidelines:
${guidelines
.map(
(guideline) => `
### Guideline ${guideline.number}: ${guideline.name}
**Description:** ${guideline.description}
**File patterns:** ${guideline.filePatterns.join(', ')}
`
)
.join('')}
## Important Notes
- Guidelines only apply to code reviews, not code completion
- Each description limited to 600 characters
- File patterns use fnmatch syntax (\`*\` wildcard)
- Manual setup required in repository settings
- Maximum 6 guidelines per repository
## Alternative Options
For file-based AI rule management, consider:
- **Claude Code**: Memory files and slash commands
- **Cursor**: .cursor/rules/ directory with MDC format
- **Windsurf**: .windsurf/rules/ directory with XML grouping
---
*Generated by VDK CLI - Setup Instructions Only*`;
return {
files: [
{
path: path.join(this.projectPath, 'GITHUB_COPILOT_SETUP.md'),
content: instructionsContent,
type: 'instructions',
scope: 'repository',
},
],
guidelines,
summary: {
totalGuidelines: guidelines.length,
maxGuidelines: 6,
requiresManualSetup: true,
enterpriseOnly: true,
approach: 'instructions-only',
},
};
}
// === Claude Code Memory Generators ===
/**
* Generate Claude Code global memory for cross-project preferences
* @param {Array} globalRules - Global core rules
* @returns {string} Global memory content
*/
generateClaudeGlobalMemory(globalRules) {
return `# Claude Code User Memory
## Coding Preferences
### Code Style
- Use consistent indentation (2-space for JS/TS/JSON/YAML, 4-space for Python/Go)
- Always use semicolons in JavaScript/TypeScript
- Prefer const/let over var
- Use descriptive variable names, avoid abbreviations
- Add JSDoc comments for complex functions
### Project Structure
- Follow conventional directory structures (src/, lib/, components/, etc.)
- Keep components small and focused (single responsibility)
- Separate business logic from UI components
- Use barrel exports (index.js/ts) for clean imports
### Testing
- Write tests for all business logic
- Use descriptive test names that explain behavior
- Group related tests with describe blocks
- Mock external dependencies in unit tests
## Development Environment
### Tools & Setup
- Primary IDE: Claude Code
- Package manager: pnpm (primary), npm (fallback)
- Use TypeScript for new JavaScript projects
- Use ESLint + Prettier for code formatting
### Git Workflow
- Use conventional commit messages (feat:, fix:, docs:, etc.)
- Create feature branches from main/master
- Squash commits before merging to main
- Always create pull/merge requests for code review
## Workflow Preferences
### Development Workflow
- Start with failing tests (TDD when appropriate)
- Run tests before committing
- Use feature flags for incomplete features
- Implement monitoring and observability
---
*Global Claude Code preferences - Applied across all projects*`;
}
generateClaudeMainMemory(coreRules, projectContext) {
const projectName = path.basename(this.projectPath);
const techStack = projectContext.techStack || {};
const content = `# ${projectName} - Claude Code Memory
## Project Overview
This project uses VDK CLI for AI assistant integration and follows specific patterns and conventions.
### Key Information
- **VDK CLI Integration**: Active
- **Primary Languages**: ${techStack.primaryLanguages?.join(', ') || 'Not detected'}
- **Frameworks**: ${techStack.frameworks?.join(', ') || 'Not detected'}
- **Architecture**: ${projectContext.patterns?.architecturalPatterns?.join(', ') || 'Standard'}
## Memory Hierarchy
@CLAUDE-patterns.md
@CLAUDE-personal.md
## Important Conventions
- All AI rules are stored in \`.ai/rules/\` directory
- Rules follow unified YAML frontmatter format
- Project follows VDK CLI naming conventions
- Memory persistence is enabled for context continuity
## VDK CLI Commands
- \`/project:analyze\` - Analyze project structure and patterns
- \`/project:refresh\` - Update VDK rules based on project changes
- \`/project:validate\` - Validate rule consistency
---
*Team-shared project memory - Last updated: ${new Date().toISOString()}*`;
return content;
}
/**
* Generate Claude Code personal preferences import file
* @returns {string} Personal preferences import content
*/
generateClaudePersonalPrefsImport() {
return `# Personal Preferences Import
## Import Personal Settings
@~/.claude/CLAUDE.md
## Project-Specific Overrides
- Personal preferences are imported automatically
- Project-specific settings take precedence
- Use this file to override global settings for this project
---
*Personal preferences import - Loads from ~/.claude/CLAUDE.md*`;
}
generateClaudeTechMemory(techRules, projectContext) {
const projectName = path.basename(this.projectPath);
let content = `# ${projectName} - Technology Patterns
## Technology-Specific Guidelines
This file contains technology and framework-specific patterns for this project.
`;
// Group rules by framework/technology
const rulesByTech = this.groupRulesByTechnology(techRules);
for (const [tech, rules] of Object.entries(rulesByTech)) {
content += `## ${tech}\n\n`;
for (const rule of rules) {
const cleanContent = this.stripFrontmatter(rule.content);
content += this.extractKeySections(cleanContent, [
'patterns',
'best practices',
'conventions',
]);
content += '\n';
}
content += '\n';
}
content += `---
*Generated by VDK CLI - Technology patterns for ${projectName}*`;
return content;
}
generateClaudeIntegrationMemory(projectContext) {
const projectName = path.basename(this.projectPath);
const techStack = projectContext.techStack || {};
return `# ${projectName} - Integration Context
## Technology Stack Integration
### Detected Stack
- **Languages**: ${techStack.primaryLanguages?.join(', ') || 'Not detected'}
- **Frameworks**: ${techStack.frameworks?.join(', ') || 'Not detected'}
- **Libraries**: ${techStack.libraries?.join(', ') || 'Not detected'}
- **Build Tools**: ${techStack.buildTools?.join(', ') || 'Not detected'}
### Integration Patterns
- VDK CLI manages AI assistant rules across multiple platforms
- Claude Code provides memory persistence and slash commands
- Project rules are automatically synchronized with team preferences
- Memory hierarchy: Project → Local → User preferences
### Available Commands
- \`/project:analyze\` - Analyze project structure and patterns
- \`/project:refresh\` - Update VDK rules based on project changes
- \`/project:validate\` - Validate rule consistency
---
*Integration context maintained by VDK CLI*`;
}
generateClaudeActiveContext(projectContext) {
return `# Active Development Context
## Current Session
- **Project**: ${path.basename(this.projectPath)}
- **Last Analysis**: ${new Date().toISOString()}
- **VDK Version**: Active
## Quick Reference
- Project memory files are active
- Technology patterns loaded
- Integration context available
## Session Notes
*This file can be used for temporary notes and context that shouldn't be shared with the team*
---
*Local memory file - not version controlled*`;
}
generateClaudeSlashCommand(rule, projectContext) {
const commandName = this.getCommandName(rule.frontmatter?.description);
const cleanContent = this.stripFrontmatter(rule.content);
return `# ${commandName}
${cleanContent}
Arguments: $ARGUMENTS
---
*Simple contextual aid with $ARGUMENTS placeholder support*`;
}
// === Cursor MDC Generators === (Duplicates removed - using methods from line 358)
// === Windsurf Memory Generators ===
generateWindsurfGlobalMemory(globalRules, projectContext) {
let content = `# Global Development Standards - VDK Enhanced
## Organization Standards
`;
for (const rule of globalRules) {
const cleanContent = this.stripFrontmatter(rule.content);
const xmlSection = this.convertToWindsurfXML(rule, cleanContent);
content += xmlSection + '\n\n';
}
content += `*Organization-wide standards - Applied across all Windsurf workspaces*`;
return content;
}
generateWindsurfRule(rule, projectContext) {
const cleanContent = this.stripFrontmatter(rule.content);
const title = this.extractTitle(cleanContent) || rule.frontmatter?.description || 'Rule';
const category = rule.frontmatter?.category || 'general';
const xmlTag = this.getWindsurfXMLTag(category);
return `<${xmlTag}>
${cleanContent}
</${xmlTag}>`;
}
convertToWindsurfXML(rule, content) {
const category = rule.frontmatter?.category || 'general';
const xmlTag = this.getWindsurfXMLTag(category);
const keyContent = this.extractKeySections(content, [
'principles',
'guidelines',
'patterns',
'best practices',
]);
return `<${xmlTag}>
${this.formatForWindsurf(keyContent)}
</${xmlTag}>`;
}
getWindsurfXMLTag(category) {
const tagMap = {
core: 'development-standards',
language: 'language-standards',
technology: 'technology-guidelines',
framework: 'technology-guidelines',
testing: 'testing-patterns',
task: 'task-workflow',
assistant: 'ai-assistance',
};
return tagMap[category] || 'rule';
}
formatForWindsurf(content) {
// Format content for Windsurf XML sections with proper indentation
return content
.split('\n')
.map((line) => (line.trim() ? `- ${line.trim()}` : ''))
.filter((line) => line)
.join('\n');
}
getWindsurfFileName(rule) {
const framework = rule.frontmatter?.framework || rule.frontmatter?.category || 'general';
return `${framework.toLowerCase().replace(/[^a-z0-9]/g, '-')}.md`;
}
/**
* Determine Windsurf activation type based on rule characteristics
* @param {Object} rule - Rule object
* @returns {string} Activation type
*/
getWindsurfActivationType(rule) {
// Manual - Via @mention in Cascade
if (rule.frontmatter?.category === 'task') return 'manual';
// Always On - Automatically applied
if (rule.frontmatter?.alwaysApply === true) return 'always-on';
// Glob - Based on file pattern matching
if (rule.frontmatter?.globs && rule.frontmatter?.globs.length > 0) return 'glob';
// Model Decision - Based on natural language description
if (rule.frontmatter?.description) return 'model-decision';
return 'model-decision'; // Default
}
/**
* Generate consolidated Windsurf rules for single-file deployment
* @param {Array} rules - Workspace rules
* @param {Object} projectContext - Project context
* @returns {string} Consolidated content
*/
generateConsolidatedWindsurfRules(rules, projectContext) {
const projectName = path.basename(this.projectPath);
let content = `# ${projectName} - Windsurf Project Rules
## Development Standards
`;
// Group rules by category for better organization
const rulesByCategory = this.groupRulesByCategory(rules);
for (const [category, categoryRules] of Object.entries(rulesByCategory)) {
const xmlTag = this.getWindsurfXMLTag(category);
content += `<${xmlTag}>\n`;
for (const rule of categoryRules) {
const cleanContent = this.stripFrontmatter(rule.content);
const keyPoints = this.extractKeyPoints(cleanContent);
content += keyPoints.map((point) => `- ${point}`).join('\n') + '\n';
}
content += `</${xmlTag}>\n\n`;
}
content += `*Consolidated project rules - Model decision activation*`;
return content;
}
/**
* Group rules by category for organized display
* @param {Array} rules - Rules to group
* @returns {Object} Rules grouped by category
*/
groupRulesByCategory(rules) {
const groups = {};
for (const rule of rules) {
const category = rule.frontmatter?.category || 'general';
if (!groups[category]) groups[category] = [];
groups[category].push(rule);
}
return groups;
}
/**
* Truncate content to character limit (HELPER for GitHub Copilot)
* @param {string} content - Content to truncate
* @param {number} limit - Character limit
* @returns {string} Truncated content
*/
truncateToCharLimit(content, limit) {
if (content.length <= limit) return content;
// Try to truncate at a sentence boundary
const truncated = content.substring(0, limit - 3);
const lastSentence = truncated.lastIndexOf('.');
if (lastSentence > limit * 0.7) {
// If we can keep at least 70% and end at sentence
return truncated.substring(0, lastSentence + 1);
}
return truncated + '...';
}
// === GitHub Copilot Generators ===
prioritizeRulesForCopilot(rules) {
// Prioritize rules based on their effectiveness for code review
return rules
.filter((rule) => rule.frontmatter?.category !== 'assistant') // Skip assistant-specific rules
.sort((a, b) => {
const aPriority = this.getCopilotPriority(a);
const bPriority = this.getCopilotPriority(b);
return bPriority - aPriority;
});
}
getCopilotPriority(rule) {
// Assign priority scores for GitHub Copilot effectiveness
const category = rule.frontmatter?.category || 'general';
const hasGlobs = rule.frontmatter?.globs && rule.frontmatter?.globs.length > 0;
let score = 0;
if (category === 'core') score += 10;
if (category === 'language' || category === 'technology') score += 8;
if (category === 'stack') score += 6;
if (category === 'task') score += 4;
if (hasGlobs) score += 3;
if (rule.frontmatter?.alwaysApply === true) score += 2;
return score;
}
generateCopilotGuideline(rule, projectContext) {
const title =
this.extractTitle(this.stripFrontmatter(rule.content)) || rule.frontmatter?.description;
const description = this.generateCopilotDescription(rule);
const paths = rule.frontmatter?.globs || [];
return {
title: title.substring(0, 100), // Reasonable title length
description: description.substring(0, 600), // GitHub Copilot limit
paths: paths,
};
}
generateCopilotDescription(rule) {
const cleanContent = this.stripFrontmatter(rule.content);
const keyPoints = this.extractKeyPoints(cleanContent);
// Create concise description focused on what Copilot should look for
let description = rule.frontmatter?.description;
if (keyPoints.length > 0) {
description += '. Key points: ' + keyPoints.slice(0, 3).join('. ');
}
return description;
}
generateCopilotDocumentation(guidelines, projectContext) {
const projectName = path.basename(this.projectPath);
let content = `# GitHub Copilot Guidelines for ${projectName}
## Overview
This directory contains GitHub Copilot Enterprise coding guidelines generated by VDK CLI.
## Active Guidelines
`;
guidelines.forEach((guideline, index) => {
content += `### ${index + 1}. ${guideline.title}
**Description**: ${guideline.description}
`;
if (guideline.paths.length > 0) {
content += `**File Patterns**: ${guideline.paths.map((p) => `\`${p}\``).join(', ')}
`;
}
});
content += `## Setup Instructions
1. Navigate to your repository on GitHub
2. Go to Settings → Code & automation → Copilot → Code review
3. Configure the guidelines listed above
4. Each guideline should be added with its title and description
## VDK Integration
- Guidelines updated automatically with \`vdk sync\`
- Project patterns detected and incorporated
- Maximum 6 guidelines per repository (GitHub Copilot limit)
---
*Generated by VDK CLI*`;
return content;
}
// === Utility Methods ===
stripFrontmatter(content) {
if (content.startsWith('---')) {
const parts = content.split('---');
return parts.slice(2).join('---').trim();
}
return content;
}
extractTitle(content) {
const lines = content.split('\n');
const titleLine = lines.find((line) => line.startsWith('# '));
return titleLine ? titleLine.replace('# ', '').trim() : 'Untitled';
}
extractKeySections(content, sectionTypes) {
const lines = content.split('\n');
let result = '';
let inTargetSection = false;
for (const line of lines) {
if (line.startsWith('## ')) {
const sectionTitle = line.toLowerCase();
inTargetSection = sectionTypes.some((type) => sectionTitle.includes(type));
}
if (inTargetSection && !line.startsWith('#')) {
result += line + '\n';
}
}
return result.trim();
}
extractKeyPoints(content) {
const lines = content.split('\n');
return lines
.filter((line) => line.trim().startsWith('-') || line.trim().startsWith('*'))
.map((line) => line.replace(/^[-*]\s*/, '').trim())
.filter((line) => line.length > 10); // Filter out very short points
}
groupRulesByTechnology(rules) {
const groups = {};
for (const rule of rules) {
const tech = rule.frontmatter?.framework || rule.frontmatter?.category || 'General';
if (!groups[tech]) groups[tech] = [];
groups[tech].push(rule);
}
return groups;
}
getCommandName(description) {
return description
.replace(/[^a-zA-Z0-9\s]/g, '')
.split(' ')
.slice(0, 3)
.join(' ')
.trim();
}
}