@intellectronica/ruler 0.3.1 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -50,11 +50,13 @@ class AbstractAgent {
50
50
  * 4. Writing the new content
51
51
  */
52
52
  async applyRulerConfig(concatenatedRules, projectRoot, rulerMcpJson, // eslint-disable-line @typescript-eslint/no-unused-vars
53
- agentConfig) {
53
+ agentConfig, backup = true) {
54
54
  const output = agentConfig?.outputPath ?? this.getDefaultOutputPath(projectRoot);
55
55
  const absolutePath = path.resolve(projectRoot, output);
56
56
  await (0, FileSystemUtils_1.ensureDirExists)(path.dirname(absolutePath));
57
- await (0, FileSystemUtils_1.backupFile)(absolutePath);
57
+ if (backup) {
58
+ await (0, FileSystemUtils_1.backupFile)(absolutePath);
59
+ }
58
60
  await (0, FileSystemUtils_1.writeGeneratedFile)(absolutePath, concatenatedRules);
59
61
  }
60
62
  /**
@@ -54,7 +54,7 @@ class AgentsMdAgent extends AbstractAgent_1.AbstractAgent {
54
54
  return path.join(projectRoot, 'AGENTS.md');
55
55
  }
56
56
  async applyRulerConfig(concatenatedRules, projectRoot, rulerMcpJson, // eslint-disable-line @typescript-eslint/no-unused-vars
57
- agentConfig) {
57
+ agentConfig, backup = true) {
58
58
  const output = agentConfig?.outputPath ?? this.getDefaultOutputPath(projectRoot);
59
59
  const absolutePath = path.resolve(projectRoot, output);
60
60
  await (0, FileSystemUtils_1.ensureDirExists)(path.dirname(absolutePath));
@@ -72,8 +72,10 @@ class AgentsMdAgent extends AbstractAgent_1.AbstractAgent {
72
72
  // No change; skip backup/write for idempotency
73
73
  return;
74
74
  }
75
- // Backup (only if file existed) then write new content
76
- await (0, FileSystemUtils_1.backupFile)(absolutePath);
75
+ // Backup (only if file existed and backup is enabled) then write new content
76
+ if (backup) {
77
+ await (0, FileSystemUtils_1.backupFile)(absolutePath);
78
+ }
77
79
  await (0, FileSystemUtils_1.writeGeneratedFile)(absolutePath, contentWithMarker);
78
80
  }
79
81
  getMcpServerKey() {
@@ -52,21 +52,23 @@ class AiderAgent {
52
52
  getName() {
53
53
  return 'Aider';
54
54
  }
55
- async applyRulerConfig(concatenatedRules, projectRoot, rulerMcpJson, agentConfig) {
55
+ async applyRulerConfig(concatenatedRules, projectRoot, rulerMcpJson, agentConfig, backup = true) {
56
56
  // First perform idempotent AGENTS.md write via composed AgentsMdAgent
57
57
  await this.agentsMdAgent.applyRulerConfig(concatenatedRules, projectRoot, null, {
58
58
  // Preserve explicit outputPath precedence semantics if provided.
59
59
  outputPath: agentConfig?.outputPath ||
60
60
  agentConfig?.outputPathInstructions ||
61
61
  undefined,
62
- });
62
+ }, backup);
63
63
  // Now handle .aider.conf.yml configuration
64
64
  const cfgPath = agentConfig?.outputPathConfig ??
65
65
  this.getDefaultOutputPath(projectRoot).config;
66
66
  let doc = {};
67
67
  try {
68
68
  await fs.access(cfgPath);
69
- await (0, FileSystemUtils_1.backupFile)(cfgPath);
69
+ if (backup) {
70
+ await (0, FileSystemUtils_1.backupFile)(cfgPath);
71
+ }
70
72
  const raw = await fs.readFile(cfgPath, 'utf8');
71
73
  doc = (yaml.load(raw) || {});
72
74
  }
@@ -48,9 +48,11 @@ class AugmentCodeAgent {
48
48
  return 'AugmentCode';
49
49
  }
50
50
  async applyRulerConfig(concatenatedRules, projectRoot, rulerMcpJson, // eslint-disable-line @typescript-eslint/no-unused-vars
51
- agentConfig) {
51
+ agentConfig, backup = true) {
52
52
  const output = agentConfig?.outputPath ?? this.getDefaultOutputPath(projectRoot);
53
- await (0, FileSystemUtils_1.backupFile)(output);
53
+ if (backup) {
54
+ await (0, FileSystemUtils_1.backupFile)(output);
55
+ }
54
56
  await (0, FileSystemUtils_1.writeGeneratedFile)(output, concatenatedRules);
55
57
  // AugmentCode does not support MCP servers
56
58
  // MCP configuration is ignored for this agent
@@ -48,7 +48,7 @@ class CursorAgent extends AbstractAgent_1.AbstractAgent {
48
48
  return 'Cursor';
49
49
  }
50
50
  async applyRulerConfig(concatenatedRules, projectRoot, rulerMcpJson, // eslint-disable-line @typescript-eslint/no-unused-vars
51
- agentConfig) {
51
+ agentConfig, backup = true) {
52
52
  const output = agentConfig?.outputPath ?? this.getDefaultOutputPath(projectRoot);
53
53
  const absolutePath = path.resolve(projectRoot, output);
54
54
  // Cursor expects a YAML front-matter block with an `alwaysApply` flag.
@@ -56,7 +56,9 @@ class CursorAgent extends AbstractAgent_1.AbstractAgent {
56
56
  const frontMatter = ['---', 'alwaysApply: true', '---', ''].join('\n');
57
57
  const content = `${frontMatter}${concatenatedRules.trimStart()}`;
58
58
  await (0, FileSystemUtils_1.ensureDirExists)(path.dirname(absolutePath));
59
- await (0, FileSystemUtils_1.backupFile)(absolutePath);
59
+ if (backup) {
60
+ await (0, FileSystemUtils_1.backupFile)(absolutePath);
61
+ }
60
62
  await (0, FileSystemUtils_1.writeGeneratedFile)(absolutePath, content);
61
63
  }
62
64
  getDefaultOutputPath(projectRoot) {
@@ -48,14 +48,16 @@ class WindsurfAgent extends AbstractAgent_1.AbstractAgent {
48
48
  return 'Windsurf';
49
49
  }
50
50
  async applyRulerConfig(concatenatedRules, projectRoot, rulerMcpJson, // eslint-disable-line @typescript-eslint/no-unused-vars
51
- agentConfig) {
51
+ agentConfig, backup = true) {
52
52
  const output = agentConfig?.outputPath ?? this.getDefaultOutputPath(projectRoot);
53
53
  const absolutePath = path.resolve(projectRoot, output);
54
54
  // Windsurf expects a YAML front-matter block with a `trigger` flag.
55
55
  const frontMatter = ['---', 'trigger: always_on', '---', ''].join('\n');
56
56
  const content = `${frontMatter}${concatenatedRules.trimStart()}`;
57
57
  await (0, FileSystemUtils_1.ensureDirExists)(path.dirname(absolutePath));
58
- await (0, FileSystemUtils_1.backupFile)(absolutePath);
58
+ if (backup) {
59
+ await (0, FileSystemUtils_1.backupFile)(absolutePath);
60
+ }
59
61
  await (0, FileSystemUtils_1.writeGeneratedFile)(absolutePath, content);
60
62
  }
61
63
  getDefaultOutputPath(projectRoot) {
@@ -64,6 +64,11 @@ function run() {
64
64
  type: 'boolean',
65
65
  description: 'Enable nested rule loading from nested .ruler directories (default: disabled)',
66
66
  default: false,
67
+ })
68
+ .option('backup', {
69
+ type: 'boolean',
70
+ description: 'Enable/disable creation of .bak backup files (default: enabled)',
71
+ default: true,
67
72
  });
68
73
  }, handlers_1.applyHandler)
69
74
  .command('init', 'Scaffold a .ruler directory with default files', (y) => {
@@ -59,6 +59,7 @@ async function applyHandler(argv) {
59
59
  const dryRun = argv['dry-run'];
60
60
  const localOnly = argv['local-only'];
61
61
  const nested = argv.nested;
62
+ const backup = argv.backup;
62
63
  // Determine gitignore preference: CLI > TOML > Default (enabled)
63
64
  // yargs handles --no-gitignore by setting gitignore to false
64
65
  let gitignorePreference;
@@ -69,7 +70,7 @@ async function applyHandler(argv) {
69
70
  gitignorePreference = undefined; // Let TOML/default decide
70
71
  }
71
72
  try {
72
- await (0, lib_1.applyAllAgentConfigs)(projectRoot, agents, configPath, mcpEnabled, mcpStrategy, gitignorePreference, verbose, dryRun, localOnly, nested);
73
+ await (0, lib_1.applyAllAgentConfigs)(projectRoot, agents, configPath, mcpEnabled, mcpStrategy, gitignorePreference, verbose, dryRun, localOnly, nested, backup);
73
74
  console.log('Ruler apply completed successfully.');
74
75
  }
75
76
  catch (err) {
@@ -218,12 +218,12 @@ function selectAgentsToRun(allAgents, config) {
218
218
  * @param cliMcpStrategy MCP strategy from CLI
219
219
  * @returns Promise resolving to array of generated file paths
220
220
  */
221
- async function processHierarchicalConfigurations(agents, configurations, verbose, dryRun, cliMcpEnabled, cliMcpStrategy) {
221
+ async function processHierarchicalConfigurations(agents, configurations, verbose, dryRun, cliMcpEnabled, cliMcpStrategy, backup = true) {
222
222
  const allGeneratedPaths = [];
223
223
  for (const config of configurations) {
224
224
  console.log(`[ruler] Processing .ruler directory: ${config.rulerDir}`);
225
225
  const rulerRoot = path.dirname(config.rulerDir);
226
- const paths = await applyConfigurationsToAgents(agents, config.concatenatedRules, config.rulerMcpJson, config.config, rulerRoot, verbose, dryRun, cliMcpEnabled, cliMcpStrategy);
226
+ const paths = await applyConfigurationsToAgents(agents, config.concatenatedRules, config.rulerMcpJson, config.config, rulerRoot, verbose, dryRun, cliMcpEnabled, cliMcpStrategy, backup);
227
227
  allGeneratedPaths.push(...paths);
228
228
  }
229
229
  return allGeneratedPaths;
@@ -240,8 +240,8 @@ async function processHierarchicalConfigurations(agents, configurations, verbose
240
240
  * @param cliMcpStrategy MCP strategy from CLI
241
241
  * @returns Promise resolving to array of generated file paths
242
242
  */
243
- async function processSingleConfiguration(agents, configuration, projectRoot, verbose, dryRun, cliMcpEnabled, cliMcpStrategy) {
244
- return await applyConfigurationsToAgents(agents, configuration.concatenatedRules, configuration.rulerMcpJson, configuration.config, projectRoot, verbose, dryRun, cliMcpEnabled, cliMcpStrategy);
243
+ async function processSingleConfiguration(agents, configuration, projectRoot, verbose, dryRun, cliMcpEnabled, cliMcpStrategy, backup = true) {
244
+ return await applyConfigurationsToAgents(agents, configuration.concatenatedRules, configuration.rulerMcpJson, configuration.config, projectRoot, verbose, dryRun, cliMcpEnabled, cliMcpStrategy, backup);
245
245
  }
246
246
  /**
247
247
  * Applies configurations to the selected agents (internal function).
@@ -254,7 +254,7 @@ async function processSingleConfiguration(agents, configuration, projectRoot, ve
254
254
  * @param dryRun Whether to perform a dry run
255
255
  * @returns Promise resolving to array of generated file paths
256
256
  */
257
- async function applyConfigurationsToAgents(agents, concatenatedRules, rulerMcpJson, config, projectRoot, verbose, dryRun, cliMcpEnabled = true, cliMcpStrategy) {
257
+ async function applyConfigurationsToAgents(agents, concatenatedRules, rulerMcpJson, config, projectRoot, verbose, dryRun, cliMcpEnabled = true, cliMcpStrategy, backup = true) {
258
258
  const generatedPaths = [];
259
259
  let agentsMdWritten = false;
260
260
  for (const agent of agents) {
@@ -266,9 +266,11 @@ async function applyConfigurationsToAgents(agents, concatenatedRules, rulerMcpJs
266
266
  const outputPaths = (0, agent_utils_1.getAgentOutputPaths)(agent, projectRoot, agentConfig);
267
267
  (0, constants_1.logVerbose)(`Agent ${agent.getName()} output paths: ${outputPaths.join(', ')}`, verbose);
268
268
  generatedPaths.push(...outputPaths);
269
- // Also add the backup file paths to the gitignore list
270
- const backupPaths = outputPaths.map((p) => `${p}.bak`);
271
- generatedPaths.push(...backupPaths);
269
+ // Only add the backup file paths to the gitignore list if backups are enabled
270
+ if (backup) {
271
+ const backupPaths = outputPaths.map((p) => `${p}.bak`);
272
+ generatedPaths.push(...backupPaths);
273
+ }
272
274
  if (dryRun) {
273
275
  (0, constants_1.logVerbose)(`DRY RUN: Would write rules to: ${outputPaths.join(', ')}`, verbose);
274
276
  }
@@ -299,15 +301,15 @@ async function applyConfigurationsToAgents(agents, concatenatedRules, rulerMcpJs
299
301
  };
300
302
  }
301
303
  if (!skipApplyForThisAgent) {
302
- await agent.applyRulerConfig(concatenatedRules, projectRoot, rulerMcpJson, finalAgentConfig);
304
+ await agent.applyRulerConfig(concatenatedRules, projectRoot, rulerMcpJson, finalAgentConfig, backup);
303
305
  }
304
306
  }
305
307
  // Handle MCP configuration
306
- await handleMcpConfiguration(agent, agentConfig, config, rulerMcpJson, projectRoot, generatedPaths, verbose, dryRun, cliMcpEnabled, cliMcpStrategy);
308
+ await handleMcpConfiguration(agent, agentConfig, config, rulerMcpJson, projectRoot, generatedPaths, verbose, dryRun, cliMcpEnabled, cliMcpStrategy, backup);
307
309
  }
308
310
  return generatedPaths;
309
311
  }
310
- async function handleMcpConfiguration(agent, agentConfig, config, rulerMcpJson, projectRoot, generatedPaths, verbose, dryRun, cliMcpEnabled = true, cliMcpStrategy) {
312
+ async function handleMcpConfiguration(agent, agentConfig, config, rulerMcpJson, projectRoot, generatedPaths, verbose, dryRun, cliMcpEnabled = true, cliMcpStrategy, backup = true) {
311
313
  if (!(0, capabilities_1.agentSupportsMcp)(agent)) {
312
314
  (0, constants_1.logVerbose)(`Agent ${agent.getName()} does not support MCP - skipping MCP configuration`, verbose);
313
315
  return;
@@ -322,47 +324,49 @@ async function handleMcpConfiguration(agent, agentConfig, config, rulerMcpJson,
322
324
  (0, constants_1.logVerbose)(`No compatible MCP servers found for ${agent.getName()} - skipping MCP configuration`, verbose);
323
325
  return;
324
326
  }
325
- await updateGitignoreForMcpFile(dest, projectRoot, generatedPaths);
326
- await applyMcpConfiguration(agent, filteredMcpJson, dest, agentConfig, config, projectRoot, cliMcpStrategy, dryRun, verbose);
327
+ await updateGitignoreForMcpFile(dest, projectRoot, generatedPaths, backup);
328
+ await applyMcpConfiguration(agent, filteredMcpJson, dest, agentConfig, config, projectRoot, cliMcpStrategy, dryRun, verbose, backup);
327
329
  }
328
- async function updateGitignoreForMcpFile(dest, projectRoot, generatedPaths) {
330
+ async function updateGitignoreForMcpFile(dest, projectRoot, generatedPaths, backup = true) {
329
331
  if (dest.startsWith(projectRoot)) {
330
332
  const relativeDest = path.relative(projectRoot, dest);
331
333
  generatedPaths.push(relativeDest);
332
- generatedPaths.push(`${relativeDest}.bak`);
334
+ if (backup) {
335
+ generatedPaths.push(`${relativeDest}.bak`);
336
+ }
333
337
  }
334
338
  }
335
- async function applyMcpConfiguration(agent, filteredMcpJson, dest, agentConfig, config, projectRoot, cliMcpStrategy, dryRun, verbose) {
339
+ async function applyMcpConfiguration(agent, filteredMcpJson, dest, agentConfig, config, projectRoot, cliMcpStrategy, dryRun, verbose, backup = true) {
336
340
  // Prevent writing MCP configs outside the project root (e.g., legacy home-directory targets)
337
341
  if (!dest.startsWith(projectRoot)) {
338
342
  (0, constants_1.logVerbose)(`Skipping MCP config for ${agent.getName()} because target path is outside project: ${dest}`, verbose);
339
343
  return;
340
344
  }
341
345
  if (agent.getIdentifier() === 'openhands') {
342
- return await applyOpenHandsMcpConfiguration(filteredMcpJson, dest, dryRun, verbose);
346
+ return await applyOpenHandsMcpConfiguration(filteredMcpJson, dest, dryRun, verbose, backup);
343
347
  }
344
348
  if (agent.getIdentifier() === 'opencode') {
345
- return await applyOpenCodeMcpConfiguration(filteredMcpJson, dest, dryRun, verbose);
349
+ return await applyOpenCodeMcpConfiguration(filteredMcpJson, dest, dryRun, verbose, backup);
346
350
  }
347
- return await applyStandardMcpConfiguration(agent, filteredMcpJson, dest, agentConfig, config, cliMcpStrategy, dryRun, verbose);
351
+ return await applyStandardMcpConfiguration(agent, filteredMcpJson, dest, agentConfig, config, cliMcpStrategy, dryRun, verbose, backup);
348
352
  }
349
- async function applyOpenHandsMcpConfiguration(filteredMcpJson, dest, dryRun, verbose) {
353
+ async function applyOpenHandsMcpConfiguration(filteredMcpJson, dest, dryRun, verbose, backup = true) {
350
354
  if (dryRun) {
351
355
  (0, constants_1.logVerbose)(`DRY RUN: Would apply MCP config by updating TOML file: ${dest}`, verbose);
352
356
  }
353
357
  else {
354
- await (0, propagateOpenHandsMcp_1.propagateMcpToOpenHands)(filteredMcpJson, dest);
358
+ await (0, propagateOpenHandsMcp_1.propagateMcpToOpenHands)(filteredMcpJson, dest, backup);
355
359
  }
356
360
  }
357
- async function applyOpenCodeMcpConfiguration(filteredMcpJson, dest, dryRun, verbose) {
361
+ async function applyOpenCodeMcpConfiguration(filteredMcpJson, dest, dryRun, verbose, backup = true) {
358
362
  if (dryRun) {
359
363
  (0, constants_1.logVerbose)(`DRY RUN: Would apply MCP config by updating OpenCode config file: ${dest}`, verbose);
360
364
  }
361
365
  else {
362
- await (0, propagateOpenCodeMcp_1.propagateMcpToOpenCode)(filteredMcpJson, dest);
366
+ await (0, propagateOpenCodeMcp_1.propagateMcpToOpenCode)(filteredMcpJson, dest, backup);
363
367
  }
364
368
  }
365
- async function applyStandardMcpConfiguration(agent, filteredMcpJson, dest, agentConfig, config, cliMcpStrategy, dryRun, verbose) {
369
+ async function applyStandardMcpConfiguration(agent, filteredMcpJson, dest, agentConfig, config, cliMcpStrategy, dryRun, verbose, backup = true) {
366
370
  const strategy = cliMcpStrategy ??
367
371
  agentConfig?.mcp?.strategy ??
368
372
  config.mcp?.strategy ??
@@ -373,6 +377,10 @@ async function applyStandardMcpConfiguration(agent, filteredMcpJson, dest, agent
373
377
  (0, constants_1.logVerbose)(`DRY RUN: Would apply MCP config to: ${dest}`, verbose);
374
378
  }
375
379
  else {
380
+ if (backup) {
381
+ const { backupFile } = await Promise.resolve().then(() => __importStar(require('../core/FileSystemUtils')));
382
+ await backupFile(dest);
383
+ }
376
384
  const existing = await (0, mcp_1.readNativeMcp)(dest);
377
385
  const merged = (0, merge_1.mergeMcp)(existing, filteredMcpJson, strategy, serverKey);
378
386
  await (0, mcp_1.writeNativeMcp)(dest, merged);
@@ -386,7 +394,7 @@ async function applyStandardMcpConfiguration(agent, filteredMcpJson, dest, agent
386
394
  * @param cliGitignoreEnabled CLI gitignore setting
387
395
  * @param dryRun Whether to perform a dry run
388
396
  */
389
- async function updateGitignore(projectRoot, generatedPaths, config, cliGitignoreEnabled, dryRun) {
397
+ async function updateGitignore(projectRoot, generatedPaths, config, cliGitignoreEnabled, dryRun, backup = true) {
390
398
  // Configuration precedence: CLI > TOML > Default (enabled)
391
399
  let gitignoreEnabled;
392
400
  if (cliGitignoreEnabled !== undefined) {
@@ -400,8 +408,10 @@ async function updateGitignore(projectRoot, generatedPaths, config, cliGitignore
400
408
  }
401
409
  if (gitignoreEnabled && generatedPaths.length > 0) {
402
410
  const uniquePaths = [...new Set(generatedPaths)];
403
- // Add wildcard pattern for backup files
404
- uniquePaths.push('*.bak');
411
+ // Add wildcard pattern for backup files only if backup is enabled
412
+ if (backup) {
413
+ uniquePaths.push('*.bak');
414
+ }
405
415
  if (uniquePaths.length > 0) {
406
416
  const prefix = (0, constants_1.actionPrefix)(dryRun);
407
417
  if (dryRun) {
package/dist/lib.js CHANGED
@@ -17,7 +17,7 @@ const agents = agents_1.allAgents;
17
17
  * @param projectRoot Root directory of the project
18
18
  * @param includedAgents Optional list of agent name filters (case-insensitive substrings)
19
19
  */
20
- async function applyAllAgentConfigs(projectRoot, includedAgents, configPath, cliMcpEnabled = true, cliMcpStrategy, cliGitignoreEnabled, verbose = false, dryRun = false, localOnly = false, nested = false) {
20
+ async function applyAllAgentConfigs(projectRoot, includedAgents, configPath, cliMcpEnabled = true, cliMcpStrategy, cliGitignoreEnabled, verbose = false, dryRun = false, localOnly = false, nested = false, backup = true) {
21
21
  // Load configuration and rules
22
22
  (0, constants_1.logVerbose)(`Loading configuration from project root: ${projectRoot}`, verbose);
23
23
  if (configPath) {
@@ -40,7 +40,7 @@ async function applyAllAgentConfigs(projectRoot, includedAgents, configPath, cli
40
40
  normalizeAgentConfigs(rootConfig, agents);
41
41
  selectedAgents = (0, apply_engine_1.selectAgentsToRun)(agents, rootConfig);
42
42
  (0, constants_1.logVerbose)(`Selected ${selectedAgents.length} agents: ${selectedAgents.map((a) => a.getName()).join(', ')}`, verbose);
43
- generatedPaths = await (0, apply_engine_1.processHierarchicalConfigurations)(selectedAgents, hierarchicalConfigs, verbose, dryRun, cliMcpEnabled, cliMcpStrategy);
43
+ generatedPaths = await (0, apply_engine_1.processHierarchicalConfigurations)(selectedAgents, hierarchicalConfigs, verbose, dryRun, cliMcpEnabled, cliMcpStrategy, backup);
44
44
  }
45
45
  else {
46
46
  const singleConfig = await (0, apply_engine_1.loadSingleConfiguration)(projectRoot, configPath, localOnly);
@@ -51,9 +51,9 @@ async function applyAllAgentConfigs(projectRoot, includedAgents, configPath, cli
51
51
  normalizeAgentConfigs(singleConfig.config, agents);
52
52
  selectedAgents = (0, apply_engine_1.selectAgentsToRun)(agents, singleConfig.config);
53
53
  (0, constants_1.logVerbose)(`Selected ${selectedAgents.length} agents: ${selectedAgents.map((a) => a.getName()).join(', ')}`, verbose);
54
- generatedPaths = await (0, apply_engine_1.processSingleConfiguration)(selectedAgents, singleConfig, projectRoot, verbose, dryRun, cliMcpEnabled, cliMcpStrategy);
54
+ generatedPaths = await (0, apply_engine_1.processSingleConfiguration)(selectedAgents, singleConfig, projectRoot, verbose, dryRun, cliMcpEnabled, cliMcpStrategy, backup);
55
55
  }
56
- await (0, apply_engine_1.updateGitignore)(projectRoot, generatedPaths, loadedConfig, cliGitignoreEnabled, dryRun);
56
+ await (0, apply_engine_1.updateGitignore)(projectRoot, generatedPaths, loadedConfig, cliGitignoreEnabled, dryRun, backup);
57
57
  }
58
58
  /**
59
59
  * Normalizes per-agent config keys to agent identifiers for consistent lookup.
@@ -85,7 +85,7 @@ function transformToOpenCodeFormat(rulerMcp) {
85
85
  mcp: openCodeServers,
86
86
  };
87
87
  }
88
- async function propagateMcpToOpenCode(rulerMcpData, openCodeConfigPath) {
88
+ async function propagateMcpToOpenCode(rulerMcpData, openCodeConfigPath, backup = true) {
89
89
  const rulerMcp = rulerMcpData || {};
90
90
  // Read existing OpenCode config if it exists
91
91
  let existingConfig = {};
@@ -108,5 +108,9 @@ async function propagateMcpToOpenCode(rulerMcpData, openCodeConfigPath) {
108
108
  },
109
109
  };
110
110
  await (0, FileSystemUtils_1.ensureDirExists)(path.dirname(openCodeConfigPath));
111
+ if (backup) {
112
+ const { backupFile } = await Promise.resolve().then(() => __importStar(require('../core/FileSystemUtils')));
113
+ await backupFile(openCodeConfigPath);
114
+ }
111
115
  await fs.writeFile(openCodeConfigPath, JSON.stringify(finalConfig, null, 2) + '\n');
112
116
  }
@@ -90,7 +90,7 @@ function normalizeRemoteServerArray(entries) {
90
90
  // All entries are strings, keep as is
91
91
  return entries;
92
92
  }
93
- async function propagateMcpToOpenHands(rulerMcpData, openHandsConfigPath) {
93
+ async function propagateMcpToOpenHands(rulerMcpData, openHandsConfigPath, backup = true) {
94
94
  const rulerMcp = rulerMcpData || {};
95
95
  // Always use the legacy Ruler MCP config format as input (top-level "mcpServers" key)
96
96
  const rulerServers = rulerMcp.mcpServers || {};
@@ -162,5 +162,9 @@ async function propagateMcpToOpenHands(rulerMcpData, openHandsConfigPath) {
162
162
  config.mcp.sse_servers = normalizeRemoteServerArray(Array.from(existingSseServers.values()));
163
163
  config.mcp.shttp_servers = normalizeRemoteServerArray(Array.from(existingShttpServers.values()));
164
164
  await (0, FileSystemUtils_1.ensureDirExists)(path.dirname(openHandsConfigPath));
165
+ if (backup) {
166
+ const { backupFile } = await Promise.resolve().then(() => __importStar(require('../core/FileSystemUtils')));
167
+ await backupFile(openHandsConfigPath);
168
+ }
165
169
  await fs.writeFile(openHandsConfigPath, (0, toml_1.stringify)(config));
166
170
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intellectronica/ruler",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "description": "Ruler — apply the same rules to all coding agents",
5
5
  "main": "dist/lib.js",
6
6
  "scripts": {