UNPKG

@intellectronica/ruler

Version:

Ruler — apply the same rules to all coding agents

233 lines (222 loc) 8.69 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.applyHandler = applyHandler; exports.initHandler = initHandler; exports.revertHandler = revertHandler; const lib_1 = require("../lib"); const revert_1 = require("../revert"); const path = __importStar(require("path")); const os = __importStar(require("os")); const fs = __importStar(require("fs/promises")); const constants_1 = require("../constants"); const ConfigLoader_1 = require("../core/ConfigLoader"); function assertNotInsideRulerDir(projectRoot) { const normalized = path.resolve(projectRoot); const segments = normalized.split(path.sep); if (segments.includes('.ruler')) { console.error(`${constants_1.ERROR_PREFIX} Cannot run from inside a .ruler directory. Please run from your project root.`); process.exit(1); } } /** * Handler for the 'apply' command. */ async function applyHandler(argv) { const projectRoot = argv['project-root']; assertNotInsideRulerDir(projectRoot); const agents = argv.agents ? argv.agents.split(',').map((a) => a.trim()) : undefined; const configPath = argv.config; const mcpEnabled = argv.mcp; const mcpStrategy = argv['mcp-overwrite'] ? 'overwrite' : undefined; const verbose = argv.verbose; const dryRun = argv['dry-run']; const localOnly = argv['local-only']; const backup = argv.backup; // Determine gitignore preference: CLI > TOML > Default (enabled) // yargs handles --no-gitignore by setting gitignore to false let gitignorePreference; if (argv.gitignore !== undefined) { gitignorePreference = argv.gitignore; } else { gitignorePreference = undefined; // Let TOML/default decide } let gitignoreLocalPreference; if (argv['gitignore-local'] !== undefined) { gitignoreLocalPreference = argv['gitignore-local']; } else { gitignoreLocalPreference = undefined; // Let TOML/default decide } // Determine nested preference: CLI > TOML > Default (false) let nested; if (argv.nested !== undefined) { // CLI explicitly set nested (either --nested or --no-nested) nested = argv.nested; } else { // CLI didn't set nested, check TOML configuration try { const config = await (0, ConfigLoader_1.loadConfig)({ projectRoot, configPath, }); // Use TOML setting if available, otherwise default to false nested = config.nested ?? false; } catch { // If config loading fails, use default (false) nested = false; } } // Determine skills preference: CLI > TOML > Default (enabled) let skillsEnabled; if (argv.skills !== undefined) { skillsEnabled = argv.skills; } else { skillsEnabled = undefined; // Let config/default decide } try { await (0, lib_1.applyAllAgentConfigs)(projectRoot, agents, configPath, mcpEnabled, mcpStrategy, gitignorePreference, verbose, dryRun, localOnly, nested, backup, skillsEnabled, gitignoreLocalPreference); console.log('Ruler apply completed successfully.'); } catch (err) { const message = err instanceof Error ? err.message : String(err); console.error(`${constants_1.ERROR_PREFIX} ${message}`); process.exit(1); } } /** * Handler for the 'init' command. */ async function initHandler(argv) { const projectRoot = argv['project-root']; const isGlobal = argv['global']; const rulerDir = isGlobal ? path.join(process.env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config'), 'ruler') : path.join(projectRoot, '.ruler'); await fs.mkdir(rulerDir, { recursive: true }); const instructionsPath = path.join(rulerDir, constants_1.DEFAULT_RULES_FILENAME); // .ruler/AGENTS.md const tomlPath = path.join(rulerDir, 'ruler.toml'); const exists = async (p) => { try { await fs.access(p); return true; } catch { return false; } }; const DEFAULT_INSTRUCTIONS = `# AGENTS.md\n\nCentralised AI agent instructions. Add coding guidelines, style guides, and project context here.\n\nRuler concatenates all .md files in this directory (and subdirectories), starting with AGENTS.md (if present), then remaining files in sorted order.\n`; const DEFAULT_TOML = `# Ruler Configuration File # See https://ai.intellectronica.net/ruler for documentation. # To specify which agents are active by default when --agents is not used, # uncomment and populate the following line. If omitted, all agents are active. # default_agents = ["copilot", "claude"] # Enable nested rule loading from nested .ruler directories # When enabled, ruler will search for and process .ruler directories throughout the project hierarchy # nested = false # [gitignore] # enabled = true # local = false # set true to write generated ignores to .git/info/exclude instead # --- Agent Specific Configurations --- # You can enable/disable agents and override their default output paths here. # Use lowercase agent identifiers: aider, amp, claude, cline, codex, copilot, cursor, jetbrains-ai, kilocode, pi, windsurf # [agents.copilot] # enabled = true # output_path = ".github/copilot-instructions.md" # [agents.aider] # enabled = true # output_path_instructions = "AGENTS.md" # output_path_config = ".aider.conf.yml" # [agents.gemini-cli] # enabled = true # --- MCP Servers --- # Define Model Context Protocol servers here. Two examples: # 1. A stdio server (local executable) # 2. A remote server (HTTP-based) # [mcp_servers.example_stdio] # command = "node" # args = ["scripts/your-mcp-server.js"] # env = { API_KEY = "replace_me" } # [mcp_servers.example_remote] # url = "https://api.example.com/mcp" # headers = { Authorization = "Bearer REPLACE_ME" } `; if (!(await exists(instructionsPath))) { // Create new AGENTS.md regardless of legacy presence. await fs.writeFile(instructionsPath, DEFAULT_INSTRUCTIONS); console.log(`[ruler] Created ${instructionsPath}`); } else { console.log(`[ruler] ${constants_1.DEFAULT_RULES_FILENAME} already exists, skipping`); } if (!(await exists(tomlPath))) { await fs.writeFile(tomlPath, DEFAULT_TOML); console.log(`[ruler] Created ${tomlPath}`); } else { console.log(`[ruler] ruler.toml already exists, skipping`); } } /** * Handler for the 'revert' command. */ async function revertHandler(argv) { const projectRoot = argv['project-root']; assertNotInsideRulerDir(projectRoot); const agents = argv.agents ? argv.agents.split(',').map((a) => a.trim()) : undefined; const configPath = argv.config; const keepBackups = argv['keep-backups']; const verbose = argv.verbose; const dryRun = argv['dry-run']; const localOnly = argv['local-only']; try { await (0, revert_1.revertAllAgentConfigs)(projectRoot, agents, configPath, keepBackups, verbose, dryRun, localOnly); } catch (err) { const message = err instanceof Error ? err.message : String(err); console.error(`${constants_1.ERROR_PREFIX} ${message}`); process.exit(1); } }