UNPKG

aiwg

Version:

Cognitive architecture for AI-augmented software development with structured memory, ensemble validation, and closed-loop correction. FAIR-aligned artifacts, 84% cost reduction via human-in-the-loop, standards adopted by 100+ organizations.

415 lines (356 loc) 12.9 kB
/** * Cursor IDE Provider * * Deploys agents, commands, skills, and rules for Cursor IDE. * Rules use native .cursor/rules/ support with MDC format. * Other artifacts use conventional .cursor/ subdirectories. * * Deployment paths: * - Agents: .cursor/agents/ * - Commands: .cursor/commands/ * - Skills: .cursor/skills/ * - Rules: .cursor/rules/ * * Special features: * - MDC format (.mdc extension) for rules * - Glob pattern attachment for rules * - $ARGUMENTS -> [arguments] conversion for rules * - Delegates rules deployment to deploy-rules-cursor.mjs */ import fs from 'fs'; import path from 'path'; import { spawn } from 'child_process'; import { ensureDir, listMdFiles, listMdFilesRecursive, listSkillDirs, deployFiles, deploySkillDir, filterAgentFiles, getAddonAgentFiles, getAddonCommandFiles, getAddonSkillDirs, getAddonRuleFiles, createAgentsMdFromTemplate, initializeFrameworkWorkspace, normalizeDeploymentMode, collectFrameworkArtifacts, cleanupOldRuleFiles } from './base.mjs'; // ============================================================================ // Provider Configuration // ============================================================================ export const name = 'cursor'; export const aliases = []; export const paths = { agents: '.cursor/agents/', commands: '.cursor/commands/', skills: '.cursor/skills/', rules: '.cursor/rules/' }; export const support = { agents: 'conventional', commands: 'conventional', skills: 'conventional', rules: 'native' }; export const capabilities = { agents: true, commands: true, skills: true, rules: true, // Rules-focused provider with native support aggregatedOutput: false, yamlFormat: false }; // ============================================================================ // Content Transformation // ============================================================================ /** * Transform agent content for Cursor * Cursor uses conventional deployment - minimal transformation needed */ export function transformAgent(srcPath, content, opts) { return content; } /** * Transform command content for Cursor * Cursor uses conventional deployment - minimal transformation needed */ export function transformCommand(srcPath, content, opts) { return content; } // ============================================================================ // Model Mapping (not applicable for Cursor) // ============================================================================ export function mapModel(shorthand, modelCfg, modelsConfig) { return shorthand; } // ============================================================================ // Deployment Functions // ============================================================================ /** * Deploy agents to .cursor/agents/ */ export function deployAgents(agentFiles, targetDir, opts) { const destDir = path.join(targetDir, paths.agents); ensureDir(destDir, opts.dryRun); return deployFiles(agentFiles, destDir, opts, transformAgent); } /** * Deploy commands to .cursor/commands/ */ export function deployCommands(commandFiles, targetDir, opts) { const destDir = path.join(targetDir, paths.commands); ensureDir(destDir, opts.dryRun); return deployFiles(commandFiles, destDir, opts, transformCommand); } /** * Deploy skills to .cursor/skills/ * Skills are directories containing SKILL.md and supporting files */ export function deploySkills(skillDirs, targetDir, opts) { const destDir = path.join(targetDir, paths.skills); ensureDir(destDir, opts.dryRun); for (const skillDir of skillDirs) { deploySkillDir(skillDir, destDir, opts); } } /** * Deploy rules via external script (native MDC support) * Falls back to inline deployment if script not found */ export async function deployRulesViaScript(targetDir, srcRoot, opts) { const scriptPath = path.join(srcRoot, 'tools', 'rules', 'deploy-rules-cursor.mjs'); if (!fs.existsSync(scriptPath)) { console.warn(`Cursor rules deployment script not found at ${scriptPath}`); return false; } console.log('Delegating rules deployment to deploy-rules-cursor.mjs...'); return new Promise((resolve, reject) => { const args = ['--target', targetDir, '--source', srcRoot]; if (opts.dryRun) args.push('--dry-run'); if (opts.force) args.push('--force'); if (opts.mode) args.push('--mode', opts.mode); const child = spawn('node', [scriptPath, ...args], { stdio: 'inherit', cwd: srcRoot }); child.on('close', (code) => { if (code === 0) resolve(true); else reject(new Error(`deploy-rules-cursor.mjs exited with code ${code}`)); }); child.on('error', reject); }); } /** * Deploy rules to .cursor/rules/ (inline deployment) * Used as fallback when external script is not available */ export function deployRulesInline(ruleFiles, targetDir, opts) { const destDir = path.join(targetDir, paths.rules); ensureDir(destDir, opts.dryRun); cleanupOldRuleFiles(destDir, opts); return deployFiles(ruleFiles, destDir, opts, transformCommand); } /** * Deploy rules - tries external script first, falls back to inline */ export async function deployRules(ruleFilesOrTarget, targetDirOrSrcRoot, optsOrUndefined) { // Handle both call signatures: // 1. deployRules(targetDir, srcRoot, opts) - from old code // 2. deployRules(ruleFiles, targetDir, opts) - from new code // Check if first arg is array (new signature) or string (old signature) if (Array.isArray(ruleFilesOrTarget)) { // New signature: deployRules(ruleFiles, targetDir, opts) return deployRulesInline(ruleFilesOrTarget, targetDirOrSrcRoot, optsOrUndefined); } else { // Old signature: deployRules(targetDir, srcRoot, opts) // Try external script first try { const success = await deployRulesViaScript(ruleFilesOrTarget, targetDirOrSrcRoot, optsOrUndefined); if (success) return; } catch (err) { console.warn('External rules script failed, using inline deployment'); } // Fallback to inline - need to collect rule files console.log('Using inline rules deployment...'); const ruleFiles = []; const srcRoot = targetDirOrSrcRoot; const opts = optsOrUndefined; // Use consolidated index if available const indexPath = getRulesIndexPath(srcRoot); if (indexPath) { ruleFiles.push(indexPath); } else { const sdlcRulesDir = path.join(srcRoot, 'agentic', 'code', 'frameworks', 'sdlc-complete', 'rules'); if (fs.existsSync(sdlcRulesDir)) { ruleFiles.push(...listMdFiles(sdlcRulesDir)); } } deployRulesInline(ruleFiles, ruleFilesOrTarget, opts); } } // ============================================================================ // AGENTS.md // ============================================================================ export function createAgentsMd(target, srcRoot, dryRun) { createAgentsMdFromTemplate(target, srcRoot, 'cursor/AGENTS.md.aiwg-template', dryRun); } // ============================================================================ // Post-Deployment // ============================================================================ export async function postDeploy(targetDir, opts) { initializeFrameworkWorkspace(targetDir, opts.mode, opts.dryRun, opts.srcRoot); if (opts.createAgentsMd) { createAgentsMd(targetDir, opts.srcRoot, opts.dryRun); } } // ============================================================================ // File Extension // ============================================================================ export function getFileExtension(type) { // Rules use .mdc for native Cursor support, everything else uses .md if (type === 'rule' || type === 'rules') { return '.mdc'; } return '.md'; } // ============================================================================ // Main Deploy Function // ============================================================================ /** * Main deployment function for Cursor provider * Orchestrates deployment of agents, commands, skills, and rules */ export async function deploy(opts) { const { srcRoot, target, mode, deployCommands: shouldDeployCommands, deploySkills: shouldDeploySkills, deployRules: shouldDeployRules, commandsOnly, skillsOnly, rulesOnly, dryRun, createAgentsMd: shouldCreateAgentsMd } = opts; console.log(`\n=== Cursor IDE Provider ===`); console.log(`Target: ${target}`); console.log(`Mode: ${mode}`); // Collect source files based on mode const agentFiles = []; const commandFiles = []; const skillDirs = []; const ruleFiles = []; // Check for addon-style directory structure (direct agents/, commands/, skills/ subdirs) // This handles deployment when --source points to an addon directory const isAddonSource = fs.existsSync(path.join(srcRoot, 'agents')) || fs.existsSync(path.join(srcRoot, 'commands')) || fs.existsSync(path.join(srcRoot, 'skills')); if (isAddonSource) { // Deploy from addon-style directory structure const addonAgentsDir = path.join(srcRoot, 'agents'); if (fs.existsSync(addonAgentsDir)) { agentFiles.push(...listMdFiles(addonAgentsDir)); } if (shouldDeployCommands || commandsOnly) { const addonCommandsDir = path.join(srcRoot, 'commands'); if (fs.existsSync(addonCommandsDir)) { commandFiles.push(...listMdFiles(addonCommandsDir)); } } if (shouldDeploySkills || skillsOnly) { const addonSkillsDir = path.join(srcRoot, 'skills'); if (fs.existsSync(addonSkillsDir)) { skillDirs.push(...listSkillDirs(addonSkillsDir)); } } if (shouldDeployRules || rulesOnly) { const addonRulesDir = path.join(srcRoot, 'rules'); if (fs.existsSync(addonRulesDir)) { ruleFiles.push(...listMdFiles(addonRulesDir)); } } } // All addons (dynamically discovered) const normalizedMode = normalizeDeploymentMode(mode); if (normalizedMode === 'general' || normalizedMode === 'sdlc' || normalizedMode === 'both' || normalizedMode === 'all') { agentFiles.push(...getAddonAgentFiles(srcRoot)); if (shouldDeployCommands || commandsOnly) { commandFiles.push(...getAddonCommandFiles(srcRoot)); } if (shouldDeploySkills || skillsOnly) { skillDirs.push(...getAddonSkillDirs(srcRoot)); } if (shouldDeployRules || rulesOnly) { ruleFiles.push(...getAddonRuleFiles(srcRoot)); } } const frameworkArtifacts = collectFrameworkArtifacts(srcRoot, normalizedMode, { includeAgents: true, includeCommands: shouldDeployCommands || commandsOnly, includeSkills: shouldDeploySkills || skillsOnly, includeRules: shouldDeployRules || rulesOnly, recursiveCommands: true, consolidatedSdlcRules: true }); agentFiles.push(...frameworkArtifacts.agents); commandFiles.push(...frameworkArtifacts.commands); skillDirs.push(...frameworkArtifacts.skills); ruleFiles.push(...frameworkArtifacts.rules); // Deploy based on flags if (!commandsOnly && !skillsOnly && !rulesOnly) { // Apply filters if specified const filteredAgents = filterAgentFiles(agentFiles, opts); if (opts.filter || opts.filterRole) { console.log(`\nFiltered from ${agentFiles.length} to ${filteredAgents.length} agents`); } console.log(`\nDeploying ${filteredAgents.length} agents...`); deployAgents(filteredAgents, target, opts); } if (shouldDeployCommands || commandsOnly) { console.log(`\nDeploying ${commandFiles.length} commands...`); deployCommands(commandFiles, target, opts); } if (shouldDeploySkills || skillsOnly) { console.log(`\nDeploying ${skillDirs.length} skills...`); deploySkills(skillDirs, target, opts); } if (shouldDeployRules || rulesOnly) { console.log(`\nDeploying ${ruleFiles.length} rules...`); // Try external script first for rules (native MDC support) try { await deployRulesViaScript(target, srcRoot, opts); } catch (err) { console.warn('External rules script failed, using inline deployment'); deployRulesInline(ruleFiles, target, opts); } } // Post-deployment await postDeploy(target, { ...opts, createAgentsMd: shouldCreateAgentsMd }); console.log('\n=== Cursor deployment complete ===\n'); } // ============================================================================ // Default Export // ============================================================================ export default { name, aliases, paths, support, capabilities, transformAgent, transformCommand, mapModel, deployAgents, deployCommands, deploySkills, deployRules, createAgentsMd, postDeploy, getFileExtension, deploy };