Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ The `apply` command looks for `.ruler/` in the current directory tree, reading t
| Option | Description |
| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--project-root <path>` | Path to your project's root (default: current directory) |
| `--agents <agent1,agent2,...>` | Comma-separated list of agent names to target (amp, copilot, claude, codex, cursor, windsurf, cline, aider, firebase, gemini-cli, junie, augmentcode, kilocode) |
| `--agents <agent1,agent2,...>` | Comma-separated list of agent names to target (amp, copilot, claude, codex, cursor, windsurf, cline, aider, firebase, gemini-cli, junie, augmentcode, kilocode, opencode, crush, goose) |
| `--config <path>` | Path to a custom `ruler.toml` configuration file |
| `--mcp` / `--with-mcp` | Enable applying MCP server configurations (default: true) |
| `--no-mcp` | Disable applying MCP server configurations |
Expand All @@ -160,6 +160,7 @@ The `apply` command looks for `.ruler/` in the current directory tree, reading t
| `--no-gitignore` | Disable automatic .gitignore updates |
| `--local-only` | Do not look for configuration in `$XDG_CONFIG_HOME` |
| `--verbose` / `-v` | Display detailed output during execution |
| `--disable-backup` | Disable creation of `.bak` backup files when applying rules (default: false, configurable via `disable_backup` in `ruler.toml`) |

### Common Examples

Expand Down Expand Up @@ -199,6 +200,12 @@ ruler apply --verbose
ruler apply --no-mcp --no-gitignore
```

**Apply rules without creating backup files:**

```bash
ruler apply --disable-backup
```

## Usage: The `revert` Command

The `revert` command safely undoes all changes made by `ruler apply`, restoring your project to its pre-ruler state. It intelligently restores files from backups (`.bak` files) when available, or removes generated files that didn't exist before.
Expand Down Expand Up @@ -275,6 +282,10 @@ Defaults to `.ruler/ruler.toml` in the project root. Override with `--config` CL
# Uses case-insensitive substring matching
default_agents = ["copilot", "claude", "aider"]

# Global backup setting - disable creation of .bak backup files
# (default: false, meaning backups are enabled by default)
disable_backup = false

# --- Global MCP Server Configuration ---
[mcp]
# Enable/disable MCP propagation globally (default: true)
Expand Down
4 changes: 2 additions & 2 deletions src/agents/AiderAgent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export class AiderAgent implements IAgent {
const mdPath =
agentConfig?.outputPathInstructions ??
this.getDefaultOutputPath(projectRoot).instructions;
await backupFile(mdPath);
await backupFile(mdPath, agentConfig?.disableBackup);
await writeGeneratedFile(mdPath, concatenatedRules);

const cfgPath =
Expand All @@ -38,7 +38,7 @@ export class AiderAgent implements IAgent {
let doc: AiderConfig = {} as AiderConfig;
try {
await fs.access(cfgPath);
await backupFile(cfgPath);
await backupFile(cfgPath, agentConfig?.disableBackup);
const raw = await fs.readFile(cfgPath, 'utf8');
doc = (yaml.load(raw) || {}) as AiderConfig;
} catch {
Expand Down
4 changes: 2 additions & 2 deletions src/agents/AugmentCodeAgent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,12 @@ export class AugmentCodeAgent implements IAgent {
): Promise<void> {
const output =
agentConfig?.outputPath ?? this.getDefaultOutputPath(projectRoot);
await backupFile(output);
await backupFile(output, agentConfig?.disableBackup);
await writeGeneratedFile(output, concatenatedRules);

if (rulerMcpJson) {
const settingsPath = getVSCodeSettingsPath(projectRoot);
await backupFile(settingsPath);
await backupFile(settingsPath, agentConfig?.disableBackup);

const existingSettings = await readVSCodeSettings(settingsPath);
const augmentServers = transformRulerToAugmentMcp(rulerMcpJson);
Expand Down
2 changes: 1 addition & 1 deletion src/agents/ClaudeAgent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export class ClaudeAgent implements IAgent {
): Promise<void> {
const output =
agentConfig?.outputPath ?? this.getDefaultOutputPath(projectRoot);
await backupFile(output);
await backupFile(output, agentConfig?.disableBackup);
await writeGeneratedFile(output, concatenatedRules);
}
getDefaultOutputPath(projectRoot: string): string {
Expand Down
2 changes: 1 addition & 1 deletion src/agents/ClineAgent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export class ClineAgent implements IAgent {
): Promise<void> {
const output =
agentConfig?.outputPath ?? this.getDefaultOutputPath(projectRoot);
await backupFile(output);
await backupFile(output, agentConfig?.disableBackup);
await writeGeneratedFile(output, concatenatedRules);
}
getDefaultOutputPath(projectRoot: string): string {
Expand Down
2 changes: 1 addition & 1 deletion src/agents/CodexCliAgent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export class CodexCliAgent implements IAgent {
defaults.instructions;

// Write the instructions file
await backupFile(instructionsPath);
await backupFile(instructionsPath, agentConfig?.disableBackup);
await writeGeneratedFile(instructionsPath, concatenatedRules);

// Handle MCP configuration if enabled
Expand Down
2 changes: 1 addition & 1 deletion src/agents/CopilotAgent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export class CopilotAgent implements IAgent {
const output =
agentConfig?.outputPath ?? this.getDefaultOutputPath(projectRoot);
await ensureDirExists(path.dirname(output));
await backupFile(output);
await backupFile(output, agentConfig?.disableBackup);
await writeGeneratedFile(output, concatenatedRules);
}
getDefaultOutputPath(projectRoot: string): string {
Expand Down
2 changes: 1 addition & 1 deletion src/agents/CursorAgent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export class CursorAgent implements IAgent {
const content = `${frontMatter}${concatenatedRules.trimStart()}`;

await ensureDirExists(path.dirname(output));
await backupFile(output);
await backupFile(output, agentConfig?.disableBackup);
await writeGeneratedFile(output, content);
}
getDefaultOutputPath(projectRoot: string): string {
Expand Down
2 changes: 1 addition & 1 deletion src/agents/FirebaseAgent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export class FirebaseAgent implements IAgent {
): Promise<void> {
const output =
agentConfig?.outputPath ?? this.getDefaultOutputPath(projectRoot);
await backupFile(output);
await backupFile(output, agentConfig?.disableBackup);
await writeGeneratedFile(output, concatenatedRules);
}

Expand Down
2 changes: 1 addition & 1 deletion src/agents/GooseAgent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export class GooseAgent implements IAgent {
this.getDefaultOutputPath(projectRoot);

// Write rules to .goosehints
await backupFile(hintsPath);
await backupFile(hintsPath, agentConfig?.disableBackup);
await writeGeneratedFile(hintsPath, concatenatedRules);
}

Expand Down
2 changes: 2 additions & 0 deletions src/agents/IAgent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ export interface IAgentConfig {
outputPathConfig?: string;
/** MCP propagation config for this agent. */
mcp?: McpConfig;
/** Disable backup file creation for this agent */
disableBackup?: boolean;
}

export interface IAgent {
Expand Down
2 changes: 1 addition & 1 deletion src/agents/JunieAgent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export class JunieAgent implements IAgent {
const output =
agentConfig?.outputPath ?? this.getDefaultOutputPath(projectRoot);
await ensureDirExists(path.dirname(output));
await backupFile(output);
await backupFile(output, agentConfig?.disableBackup);
await writeGeneratedFile(output, concatenatedRules);
}
getDefaultOutputPath(projectRoot: string): string {
Expand Down
2 changes: 1 addition & 1 deletion src/agents/KiloCodeAgent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export class KiloCodeAgent implements IAgent {
const output =
agentConfig?.outputPath ?? this.getDefaultOutputPath(projectRoot);
await ensureDirExists(path.dirname(output));
await backupFile(output);
await backupFile(output, agentConfig?.disableBackup);
await writeGeneratedFile(output, concatenatedRules);
}

Expand Down
2 changes: 1 addition & 1 deletion src/agents/OpenCodeAgent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export class OpenCodeAgent implements IAgent {
const outputPath =
agentConfig?.outputPath ?? this.getDefaultOutputPath(projectRoot);
const absolutePath = path.resolve(projectRoot, outputPath);
await backupFile(absolutePath);
await backupFile(absolutePath, agentConfig?.disableBackup);
await writeGeneratedFile(absolutePath, concatenatedRules);
}

Expand Down
2 changes: 1 addition & 1 deletion src/agents/OpenHandsAgent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export class OpenHandsAgent implements IAgent {
const output =
agentConfig?.outputPath ?? this.getDefaultOutputPath(projectRoot);
await ensureDirExists(path.dirname(output));
await backupFile(output);
await backupFile(output, agentConfig?.disableBackup);
await writeGeneratedFile(output, concatenatedRules);
}
getDefaultOutputPath(projectRoot: string): string {
Expand Down
2 changes: 1 addition & 1 deletion src/agents/WindsurfAgent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export class WindsurfAgent implements IAgent {
const output =
agentConfig?.outputPath ?? this.getDefaultOutputPath(projectRoot);
await ensureDirExists(path.dirname(output));
await backupFile(output);
await backupFile(output, agentConfig?.disableBackup);
await writeGeneratedFile(output, concatenatedRules);
}
getDefaultOutputPath(projectRoot: string): string {
Expand Down
14 changes: 14 additions & 0 deletions src/cli/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@ export function run(): void {
'Only search for local .ruler directories, ignore global config',
default: false,
});
y.option('disable-backup', {
type: 'boolean',
description:
'Disable creation of backup files before applying changes',
default: false,
});
},
async (argv) => {
const projectRoot = argv['project-root'] as string;
Expand All @@ -79,6 +85,13 @@ export function run(): void {
const verbose = argv.verbose as boolean;
const dryRun = argv['dry-run'] as boolean;
const localOnly = argv['local-only'] as boolean;
// Determine backup disable preference: CLI > TOML > Default (false)
let backupDisablePreference: boolean | undefined;
if (argv['disable-backup'] !== undefined) {
backupDisablePreference = argv['disable-backup'] as boolean;
} else {
backupDisablePreference = undefined; // Let TOML/default decide
}

// Determine gitignore preference: CLI > TOML > Default (enabled)
// yargs handles --no-gitignore by setting gitignore to false
Expand All @@ -99,6 +112,7 @@ export function run(): void {
verbose,
dryRun,
localOnly,
backupDisablePreference,
);
console.log('Ruler apply completed successfully.');
} catch (err: unknown) {
Expand Down
8 changes: 8 additions & 0 deletions src/core/ConfigLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ const rulerConfigSchema = z.object({
enabled: z.boolean().optional(),
})
.optional(),
disable_backup: z.boolean().optional(),
});

/**
Expand Down Expand Up @@ -69,6 +70,8 @@ export interface LoadedConfig {
mcp?: GlobalMcpConfig;
/** Gitignore configuration section. */
gitignore?: GitignoreConfig;
/** Global disable backup setting. */
disableBackup?: boolean;
}

/**
Expand Down Expand Up @@ -207,11 +210,16 @@ export async function loadConfig(
gitignoreConfig.enabled = rawGitignoreSection.enabled;
}

// Parse global disable_backup setting
const disableBackup =
typeof raw.disable_backup === 'boolean' ? raw.disable_backup : undefined;

return {
defaultAgents,
agentConfigs,
cliAgents,
mcp: globalMcpConfig,
gitignore: gitignoreConfig,
disableBackup,
};
}
10 changes: 9 additions & 1 deletion src/core/FileSystemUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,16 @@ export async function writeGeneratedFile(

/**
* Creates a backup of the given filePath by copying it to filePath.bak if it exists.
* @param filePath The file to backup
* @param disableBackup If true, skip creating the backup
*/
export async function backupFile(filePath: string): Promise<void> {
export async function backupFile(
filePath: string,
disableBackup: boolean = false,
): Promise<void> {
if (disableBackup) {
return; // Skip backup if disabled
}
try {
await fs.access(filePath);
await fs.copyFile(filePath, `${filePath}.bak`);
Expand Down
17 changes: 15 additions & 2 deletions src/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ export async function applyAllAgentConfigs(
verbose = false,
dryRun = false,
localOnly = false,
cliDisableBackup?: boolean,
): Promise<void> {
// Load configuration (default_agents, per-agent overrides, CLI filters)
logVerbose(
Expand Down Expand Up @@ -275,6 +276,17 @@ export async function applyAllAgentConfigs(
);
}

// Handle backup disable setting
// Configuration precedence: CLI > TOML > Default (false)
let disableBackup: boolean;
if (cliDisableBackup !== undefined) {
disableBackup = cliDisableBackup;
} else if (config.disableBackup !== undefined) {
disableBackup = config.disableBackup;
} else {
disableBackup = false; // Default disabled (backups enabled)
}

// Collect all generated file paths for .gitignore
const generatedPaths: string[] = [];
let agentsMdWritten = false;
Expand Down Expand Up @@ -312,7 +324,8 @@ export async function applyAllAgentConfigs(
}
agentsMdWritten = true;
}
let finalAgentConfig = agentConfig;
// Propagate disableBackup to agent config
let finalAgentConfig = { ...agentConfig, disableBackup };
if (agent.getIdentifier() === 'augmentcode' && rulerMcpJson) {
const resolvedStrategy =
cliMcpStrategy ??
Expand All @@ -321,7 +334,7 @@ export async function applyAllAgentConfigs(
'merge';

finalAgentConfig = {
...agentConfig,
...finalAgentConfig,
mcp: {
...agentConfig?.mcp,
strategy: resolvedStrategy,
Expand Down
Loading
Loading